Continue migration of methods from handler to aiohasupervisor (#129183)

This commit is contained in:
Mike Degatano 2024-10-29 09:33:21 -04:00 committed by GitHub
parent 79c602f59c
commit 673f0224c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 906 additions and 667 deletions

View File

@ -10,6 +10,7 @@ import os
import re
from typing import Any, NamedTuple
from aiohasupervisor import SupervisorError
import voluptuous as vol
from homeassistant.auth.const import GROUP_ID_ADMIN
@ -101,16 +102,12 @@ from .handler import ( # noqa: F401
HassIO,
HassioAPIError,
async_create_backup,
async_get_addon_discovery_info,
async_get_green_settings,
async_get_yellow_settings,
async_reboot_host,
async_set_green_settings,
async_set_yellow_settings,
async_update_core,
async_update_diagnostics,
async_update_os,
async_update_supervisor,
get_supervisor_client,
)
from .http import HassIOView
@ -310,8 +307,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
host = os.environ["SUPERVISOR"]
websession = async_get_clientsession(hass)
hass.data[DOMAIN] = hassio = HassIO(hass.loop, websession, host)
supervisor_client = get_supervisor_client(hass)
if not await hassio.is_connected():
try:
await supervisor_client.supervisor.ping()
except SupervisorError:
_LOGGER.warning("Not connected with the supervisor / system too busy!")
store = Store[dict[str, str]](hass, STORAGE_VERSION, STORAGE_KEY)
@ -468,9 +468,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
async def _async_stop(hass: HomeAssistant, restart: bool) -> None:
"""Stop or restart home assistant."""
if restart:
await hassio.restart_homeassistant()
await supervisor_client.homeassistant.restart()
else:
await hassio.stop_homeassistant()
await supervisor_client.homeassistant.stop()
# Set a custom handler for the homeassistant.restart and homeassistant.stop services
async_set_stop_handler(hass, _async_stop)

View File

@ -21,12 +21,7 @@ from aiohasupervisor.models import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from .handler import (
HassioAPIError,
async_create_backup,
async_get_addon_discovery_info,
get_supervisor_client,
)
from .handler import HassioAPIError, async_create_backup, get_supervisor_client
type _FuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], Awaitable[_R]]
type _ReturnFuncType[_T, **_P, _R] = Callable[
@ -128,18 +123,25 @@ class AddonManager:
)
)
@api_error("Failed to get the {addon_name} add-on discovery info")
@api_error(
"Failed to get the {addon_name} add-on discovery info",
expected_error_type=SupervisorError,
)
async def async_get_addon_discovery_info(self) -> dict:
"""Return add-on discovery info."""
discovery_info = await async_get_addon_discovery_info(
self._hass, self.addon_slug
discovery_info = next(
(
msg
for msg in await self._supervisor_client.discovery.list()
if msg.addon == self.addon_slug
),
None,
)
if not discovery_info:
raise AddonError(f"Failed to get {self.addon_name} add-on discovery info")
discovery_info_config: dict = discovery_info["config"]
return discovery_info_config
return discovery_info.config
@api_error(
"Failed to get the {addon_name} add-on info",

View File

@ -563,8 +563,8 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
# updates if this is not a scheduled refresh and
# we are not doing the first refresh.
try:
await self.hassio.refresh_updates()
except HassioAPIError as err:
await self.supervisor_client.refresh_updates()
except SupervisorError as err:
_LOGGER.warning("Error on Supervisor API: %s", err)
await super()._async_refresh(

View File

@ -8,6 +8,7 @@ import logging
from typing import Any
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import Discovery
from aiohttp import web
from aiohttp.web_exceptions import HTTPServiceUnavailable
@ -19,8 +20,8 @@ from homeassistant.data_entry_flow import BaseServiceInfo
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, get_supervisor_client
from .const import ATTR_ADDON, ATTR_UUID, DOMAIN
from .handler import HassIO, get_supervisor_client
_LOGGER = logging.getLogger(__name__)
@ -39,20 +40,21 @@ class HassioServiceInfo(BaseServiceInfo):
def async_setup_discovery_view(hass: HomeAssistant, hassio: HassIO) -> None:
"""Discovery setup."""
hassio_discovery = HassIODiscovery(hass, hassio)
supervisor_client = get_supervisor_client(hass)
hass.http.register_view(hassio_discovery)
# Handle exists discovery messages
async def _async_discovery_start_handler(event: Event) -> None:
"""Process all exists discovery on startup."""
try:
data = await hassio.retrieve_discovery_messages()
except HassioAPIError as err:
data = await supervisor_client.discovery.list()
except SupervisorError as err:
_LOGGER.error("Can't read discover info: %s", err)
return
jobs = [
asyncio.create_task(hassio_discovery.async_process_new(discovery))
for discovery in data[ATTR_DISCOVERY]
for discovery in data
]
if jobs:
await asyncio.wait(jobs)
@ -95,8 +97,8 @@ class HassIODiscovery(HomeAssistantView):
"""Handle new discovery requests."""
# Fetch discovery data and prevent injections
try:
data = await self.hassio.get_discovery_message(uuid)
except HassioAPIError as err:
data = await self._supervisor_client.discovery.get(uuid)
except SupervisorError as err:
_LOGGER.error("Can't read discovery data: %s", err)
raise HTTPServiceUnavailable from None
@ -113,52 +115,50 @@ class HassIODiscovery(HomeAssistantView):
async def async_rediscover(self, uuid: str) -> None:
"""Rediscover add-on when config entry is removed."""
try:
data = await self.hassio.get_discovery_message(uuid)
except HassioAPIError as err:
data = await self._supervisor_client.discovery.get(uuid)
except SupervisorError as err:
_LOGGER.debug("Can't read discovery data: %s", err)
else:
await self.async_process_new(data)
async def async_process_new(self, data: dict[str, Any]) -> None:
async def async_process_new(self, data: Discovery) -> None:
"""Process add discovery entry."""
service: str = data[ATTR_SERVICE]
config_data: dict[str, Any] = data[ATTR_CONFIG]
slug: str = data[ATTR_ADDON]
uuid: str = data[ATTR_UUID]
# Read additional Add-on info
try:
addon_info = await self._supervisor_client.addons.addon_info(slug)
addon_info = await self._supervisor_client.addons.addon_info(data.addon)
except SupervisorError as err:
_LOGGER.error("Can't read add-on info: %s", err)
return
config_data[ATTR_ADDON] = addon_info.name
data.config[ATTR_ADDON] = addon_info.name
# Use config flow
discovery_flow.async_create_flow(
self.hass,
service,
data.service,
context={"source": config_entries.SOURCE_HASSIO},
data=HassioServiceInfo(
config=config_data, name=addon_info.name, slug=slug, uuid=uuid
config=data.config,
name=addon_info.name,
slug=data.addon,
uuid=data.uuid,
),
discovery_key=discovery_flow.DiscoveryKey(
domain=DOMAIN,
key=data[ATTR_UUID],
key=data.uuid,
version=1,
),
)
async def async_process_del(self, data: dict[str, Any]) -> None:
"""Process remove discovery entry."""
service = data[ATTR_SERVICE]
uuid = data[ATTR_UUID]
service: str = data[ATTR_SERVICE]
uuid: str = data[ATTR_UUID]
# Check if really deletet / prevent injections
try:
data = await self.hassio.get_discovery_message(uuid)
except HassioAPIError:
data = await self._supervisor_client.discovery.get(uuid)
except SupervisorError:
pass
else:
_LOGGER.warning("Retrieve wrong unload for %s", service)

View File

@ -24,7 +24,7 @@ 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
from .const import ATTR_MESSAGE, ATTR_RESULT, DOMAIN, X_HASS_SOURCE
_LOGGER = logging.getLogger(__name__)
@ -76,15 +76,6 @@ async def async_update_diagnostics(hass: HomeAssistant, diagnostics: bool) -> bo
return await hassio.update_diagnostics(diagnostics)
@bind_hass
async def async_get_addon_discovery_info(hass: HomeAssistant, slug: str) -> dict | None:
"""Return discovery data for an add-on."""
hassio: HassIO = hass.data[DOMAIN]
data = await hassio.retrieve_discovery_messages()
discovered_addons = data[ATTR_DISCOVERY]
return next((addon for addon in discovered_addons if addon["addon"] == slug), None)
@bind_hass
@api_data
async def async_create_backup(
@ -100,52 +91,6 @@ async def async_create_backup(
return await hassio.send_command(command, payload=payload, timeout=None)
@bind_hass
@api_data
async def async_update_os(hass: HomeAssistant, version: str | None = None) -> dict:
"""Update Home Assistant Operating System.
The caller of the function should handle HassioAPIError.
"""
hassio: HassIO = hass.data[DOMAIN]
command = "/os/update"
return await hassio.send_command(
command,
payload={"version": version},
timeout=None,
)
@bind_hass
@api_data
async def async_update_supervisor(hass: HomeAssistant) -> dict:
"""Update Home Assistant Supervisor.
The caller of the function should handle HassioAPIError.
"""
hassio: HassIO = hass.data[DOMAIN]
command = "/supervisor/update"
return await hassio.send_command(command, timeout=None)
@bind_hass
@api_data
async def async_update_core(
hass: HomeAssistant, version: str | None = None, backup: bool = False
) -> dict:
"""Update Home Assistant Core.
The caller of the function should handle HassioAPIError.
"""
hassio: HassIO = hass.data[DOMAIN]
command = "/core/update"
return await hassio.send_command(
command,
payload={"version": version, "backup": backup},
timeout=None,
)
@bind_hass
@_api_bool
async def async_apply_suggestion(hass: HomeAssistant, suggestion_uuid: str) -> dict:
@ -228,14 +173,6 @@ class HassIO:
"""Return base url for Supervisor."""
return self._base_url
@_api_bool
def is_connected(self) -> Coroutine:
"""Return true if it connected to Hass.io supervisor.
This method returns a coroutine.
"""
return self.send_command("/supervisor/ping", method="get", timeout=15)
@api_data
def get_info(self) -> Coroutine:
"""Return generic Supervisor information.
@ -308,46 +245,6 @@ class HassIO:
"""
return self.send_command("/ingress/panels", method="get")
@_api_bool
def restart_homeassistant(self) -> Coroutine:
"""Restart Home-Assistant container.
This method returns a coroutine.
"""
return self.send_command("/homeassistant/restart")
@_api_bool
def stop_homeassistant(self) -> Coroutine:
"""Stop Home-Assistant container.
This method returns a coroutine.
"""
return self.send_command("/homeassistant/stop")
@_api_bool
def refresh_updates(self) -> Coroutine:
"""Refresh available updates.
This method returns a coroutine.
"""
return self.send_command("/refresh_updates", timeout=300)
@api_data
def retrieve_discovery_messages(self) -> Coroutine:
"""Return all discovery data from Hass.io API.
This method returns a coroutine.
"""
return self.send_command("/discovery", method="get", timeout=60)
@api_data
def get_discovery_message(self, uuid: str) -> Coroutine:
"""Return a single discovery data message.
This method returns a coroutine.
"""
return self.send_command(f"/discovery/{uuid}", method="get")
@api_data
def get_resolution_info(self) -> Coroutine:
"""Return data for Supervisor resolution center.

View File

@ -5,7 +5,11 @@ from __future__ import annotations
from typing import Any
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import StoreAddonUpdate
from aiohasupervisor.models import (
HomeAssistantUpdateOptions,
OSUpdate,
StoreAddonUpdate,
)
from awesomeversion import AwesomeVersion, AwesomeVersionStrategy
from homeassistant.components.update import (
@ -36,12 +40,6 @@ from .entity import (
HassioOSEntity,
HassioSupervisorEntity,
)
from .handler import (
HassioAPIError,
async_update_core,
async_update_os,
async_update_supervisor,
)
ENTITY_DESCRIPTION = UpdateEntityDescription(
name="Update",
@ -213,8 +211,10 @@ class SupervisorOSUpdateEntity(HassioOSEntity, UpdateEntity):
) -> None:
"""Install an update."""
try:
await async_update_os(self.hass, version)
except HassioAPIError as err:
await self.coordinator.supervisor_client.os.update(
OSUpdate(version=version)
)
except SupervisorError as err:
raise HomeAssistantError(
f"Error updating Home Assistant Operating System: {err}"
) from err
@ -259,8 +259,8 @@ class SupervisorSupervisorUpdateEntity(HassioSupervisorEntity, UpdateEntity):
) -> None:
"""Install an update."""
try:
await async_update_supervisor(self.hass)
except HassioAPIError as err:
await self.coordinator.supervisor_client.supervisor.update()
except SupervisorError as err:
raise HomeAssistantError(
f"Error updating Home Assistant Supervisor: {err}"
) from err
@ -304,8 +304,10 @@ class SupervisorCoreUpdateEntity(HassioCoreEntity, UpdateEntity):
) -> None:
"""Install an update."""
try:
await async_update_core(self.hass, version=version, backup=backup)
except HassioAPIError as err:
await self.coordinator.supervisor_client.homeassistant.update(
HomeAssistantUpdateOptions(version=version, backup=backup)
)
except SupervisorError as err:
raise HomeAssistantError(
f"Error updating Home Assistant Core: {err}"
) from err

View File

@ -8,7 +8,7 @@ from pathlib import Path
from typing import TYPE_CHECKING, Any
from unittest.mock import AsyncMock, MagicMock, patch
from aiohasupervisor.models import Repository, StoreAddon, StoreInfo
from aiohasupervisor.models import Discovery, Repository, StoreAddon, StoreInfo
import pytest
from homeassistant.config_entries import (
@ -205,12 +205,9 @@ def addon_manager_fixture(
@pytest.fixture(name="discovery_info")
def discovery_info_fixture() -> Any:
def discovery_info_fixture() -> list[Discovery]:
"""Return the discovery info from the supervisor."""
# pylint: disable-next=import-outside-toplevel
from .hassio.common import mock_discovery_info
return mock_discovery_info()
return []
@pytest.fixture(name="discovery_info_side_effect")
@ -221,13 +218,29 @@ def discovery_info_side_effect_fixture() -> Any | None:
@pytest.fixture(name="get_addon_discovery_info")
def get_addon_discovery_info_fixture(
discovery_info: dict[str, Any], discovery_info_side_effect: Any | None
) -> Generator[AsyncMock]:
supervisor_client: AsyncMock,
discovery_info: list[Discovery],
discovery_info_side_effect: Any | None,
) -> AsyncMock:
"""Mock get add-on discovery info."""
# pylint: disable-next=import-outside-toplevel
from .hassio.common import mock_get_addon_discovery_info
supervisor_client.discovery.list.return_value = discovery_info
supervisor_client.discovery.list.side_effect = discovery_info_side_effect
return supervisor_client.discovery.list
yield from mock_get_addon_discovery_info(discovery_info, discovery_info_side_effect)
@pytest.fixture(name="get_discovery_message_side_effect")
def get_discovery_message_side_effect_fixture() -> Any | None:
"""Side effect for getting a discovery message by uuid."""
return None
@pytest.fixture(name="get_discovery_message")
def get_discovery_message_fixture(
supervisor_client: AsyncMock, get_discovery_message_side_effect: Any | None
) -> AsyncMock:
"""Mock getting a discovery message by uuid."""
supervisor_client.discovery.get.side_effect = get_discovery_message_side_effect
return supervisor_client.discovery.get
@pytest.fixture(name="addon_store_info_side_effect")
@ -453,11 +466,22 @@ def addon_changelog_fixture(supervisor_client: AsyncMock) -> AsyncMock:
return supervisor_client.store.addon_changelog
@pytest.fixture(name="supervisor_is_connected")
def supervisor_is_connected_fixture(supervisor_client: AsyncMock) -> AsyncMock:
"""Mock supervisor is connected."""
supervisor_client.supervisor.ping.return_value = None
return supervisor_client.supervisor.ping
@pytest.fixture(name="supervisor_client")
def supervisor_client() -> Generator[AsyncMock]:
"""Mock the supervisor client."""
supervisor_client = AsyncMock()
supervisor_client.addons = AsyncMock()
supervisor_client.discovery = AsyncMock()
supervisor_client.homeassistant = AsyncMock()
supervisor_client.os = AsyncMock()
supervisor_client.supervisor = AsyncMock()
with (
patch(
"homeassistant.components.hassio.get_supervisor_client",

View File

@ -7,7 +7,7 @@ from dataclasses import fields
import logging
from types import MethodType
from typing import Any
from unittest.mock import DEFAULT, AsyncMock, Mock, patch
from unittest.mock import AsyncMock, Mock, patch
from aiohasupervisor.models import (
AddonsOptions,
@ -75,23 +75,6 @@ def mock_addon_manager(hass: HomeAssistant) -> AddonManager:
return AddonManager(hass, LOGGER, "Test", "test_addon")
def mock_discovery_info() -> Any:
"""Return the discovery info from the supervisor."""
return DEFAULT
def mock_get_addon_discovery_info(
discovery_info: dict[str, Any], discovery_info_side_effect: Any | None
) -> Generator[AsyncMock]:
"""Mock get add-on discovery info."""
with patch(
"homeassistant.components.hassio.addon_manager.async_get_addon_discovery_info",
side_effect=discovery_info_side_effect,
return_value=discovery_info,
) as get_addon_discovery_info:
yield get_addon_discovery_info
def mock_addon_store_info(
supervisor_client: AsyncMock,
addon_store_info_side_effect: Any | None,

View File

@ -32,14 +32,10 @@ def disable_security_filter() -> Generator[None]:
@pytest.fixture
def hassio_env() -> Generator[None]:
def hassio_env(supervisor_is_connected: AsyncMock) -> Generator[None]:
"""Fixture to inject hassio env."""
with (
patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}),
patch(
"homeassistant.components.hassio.HassIO.is_connected",
return_value={"result": "ok", "data": {}},
),
patch.dict(os.environ, {"SUPERVISOR_TOKEN": SUPERVISOR_TOKEN}),
patch(
"homeassistant.components.hassio.HassIO.get_info",
@ -78,9 +74,6 @@ def hassio_stubs(
patch(
"homeassistant.components.hassio.issues.SupervisorIssues.setup",
),
patch(
"homeassistant.components.hassio.HassIO.refresh_updates",
),
):
hass.set_state(CoreState.starting)
hass.loop.run_until_complete(async_setup_component(hass, "hassio", {}))
@ -144,7 +137,6 @@ def 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"})
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/info",
@ -225,7 +217,6 @@ def all_setup_requests(
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
addon_installed.return_value.update_available = False
addon_installed.return_value.version = "1.0.0"

View File

@ -5,9 +5,10 @@ from __future__ import annotations
import asyncio
from typing import Any
from unittest.mock import AsyncMock, call
from uuid import uuid4
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import AddonsOptions
from aiohasupervisor.models import AddonsOptions, Discovery
import pytest
from homeassistant.components.hassio.addon_manager import (
@ -62,7 +63,11 @@ async def test_get_addon_discovery_info(
addon_manager: AddonManager, get_addon_discovery_info: AsyncMock
) -> None:
"""Test get addon discovery info."""
get_addon_discovery_info.return_value = {"config": {"test_key": "test"}}
get_addon_discovery_info.return_value = [
Discovery(
addon="test_addon", service="", uuid=uuid4(), config={"test_key": "test"}
)
]
assert await addon_manager.async_get_addon_discovery_info() == {"test_key": "test"}
@ -73,8 +78,6 @@ async def test_missing_addon_discovery_info(
addon_manager: AddonManager, get_addon_discovery_info: AsyncMock
) -> None:
"""Test missing addon discovery info."""
get_addon_discovery_info.return_value = None
with pytest.raises(AddonError):
await addon_manager.async_get_addon_discovery_info()
@ -85,7 +88,7 @@ async def test_get_addon_discovery_info_error(
addon_manager: AddonManager, get_addon_discovery_info: AsyncMock
) -> None:
"""Test get addon discovery info raises error."""
get_addon_discovery_info.side_effect = HassioAPIError("Boom")
get_addon_discovery_info.side_effect = SupervisorError("Boom")
with pytest.raises(AddonError) as err:
assert await addon_manager.async_get_addon_discovery_info()

View File

@ -1,7 +1,7 @@
"""Test add-on panel."""
from http import HTTPStatus
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
@ -13,10 +13,11 @@ from tests.typing import ClientSessionGenerator
@pytest.fixture(autouse=True)
def mock_all(aioclient_mock: AiohttpClientMocker) -> None:
def mock_all(
aioclient_mock: AiohttpClientMocker, supervisor_is_connected: 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"})
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info",

View File

@ -28,7 +28,6 @@ def mock_all(
) -> 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"})
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/info",
@ -141,7 +140,6 @@ def mock_all(
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={

View File

@ -27,7 +27,6 @@ def mock_all(
) -> 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"})
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/info",
@ -144,7 +143,6 @@ def mock_all(
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={

View File

@ -3,7 +3,9 @@
from collections.abc import Generator
from http import HTTPStatus
from unittest.mock import AsyncMock, Mock, patch
from uuid import uuid4
from aiohasupervisor.models import Discovery
from aiohttp.test_utils import TestClient
import pytest
@ -48,42 +50,34 @@ def mock_mqtt_fixture(
@pytest.mark.usefixtures("hassio_client")
async def test_hassio_discovery_startup(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
mock_mqtt: type[config_entries.ConfigFlow],
addon_installed: AsyncMock,
get_addon_discovery_info: AsyncMock,
) -> None:
"""Test startup and discovery after event."""
aioclient_mock.get(
"http://127.0.0.1/discovery",
json={
"result": "ok",
"data": {
"discovery": [
{
"service": "mqtt",
"uuid": "test",
"addon": "mosquitto",
"config": {
"broker": "mock-broker",
"port": 1883,
"username": "mock-user",
"password": "mock-pass",
"protocol": "3.1.1",
},
}
]
get_addon_discovery_info.return_value = [
Discovery(
addon="mosquitto",
service="mqtt",
uuid=(uuid := uuid4()),
config={
"broker": "mock-broker",
"port": 1883,
"username": "mock-user",
"password": "mock-pass",
"protocol": "3.1.1",
},
},
)
)
]
addon_installed.return_value.name = "Mosquitto Test"
assert aioclient_mock.call_count == 0
assert get_addon_discovery_info.call_count == 0
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 1
assert get_addon_discovery_info.call_count == 1
assert mock_mqtt.async_step_hassio.called
mock_mqtt.async_step_hassio.assert_called_with(
HassioServiceInfo(
@ -97,7 +91,7 @@ async def test_hassio_discovery_startup(
},
name="Mosquitto Test",
slug="mosquitto",
uuid="test",
uuid=uuid,
)
)
@ -108,34 +102,27 @@ async def test_hassio_discovery_startup_done(
aioclient_mock: AiohttpClientMocker,
mock_mqtt: type[config_entries.ConfigFlow],
addon_installed: AsyncMock,
get_addon_discovery_info: AsyncMock,
) -> None:
"""Test startup and discovery with hass discovery."""
aioclient_mock.post(
"http://127.0.0.1/supervisor/options",
json={"result": "ok", "data": {}},
)
aioclient_mock.get(
"http://127.0.0.1/discovery",
json={
"result": "ok",
"data": {
"discovery": [
{
"service": "mqtt",
"uuid": "test",
"addon": "mosquitto",
"config": {
"broker": "mock-broker",
"port": 1883,
"username": "mock-user",
"password": "mock-pass",
"protocol": "3.1.1",
},
}
]
get_addon_discovery_info.return_value = [
Discovery(
addon="mosquitto",
service="mqtt",
uuid=(uuid := uuid4()),
config={
"broker": "mock-broker",
"port": 1883,
"username": "mock-user",
"password": "mock-pass",
"protocol": "3.1.1",
},
},
)
)
]
addon_installed.return_value.name = "Mosquitto Test"
with (
@ -152,7 +139,7 @@ async def test_hassio_discovery_startup_done(
await async_setup_component(hass, "hassio", {})
await hass.async_block_till_done()
assert aioclient_mock.call_count == 1
assert get_addon_discovery_info.call_count == 1
assert mock_mqtt.async_step_hassio.called
mock_mqtt.async_step_hassio.assert_called_with(
HassioServiceInfo(
@ -166,35 +153,29 @@ async def test_hassio_discovery_startup_done(
},
name="Mosquitto Test",
slug="mosquitto",
uuid="test",
uuid=uuid,
)
)
async def test_hassio_discovery_webhook(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
hassio_client: TestClient,
mock_mqtt: type[config_entries.ConfigFlow],
addon_installed: AsyncMock,
get_discovery_message: AsyncMock,
) -> None:
"""Test discovery webhook."""
aioclient_mock.get(
"http://127.0.0.1/discovery/testuuid",
json={
"result": "ok",
"data": {
"service": "mqtt",
"uuid": "test",
"addon": "mosquitto",
"config": {
"broker": "mock-broker",
"port": 1883,
"username": "mock-user",
"password": "mock-pass",
"protocol": "3.1.1",
},
},
get_discovery_message.return_value = Discovery(
addon="mosquitto",
service="mqtt",
uuid=(uuid := uuid4()),
config={
"broker": "mock-broker",
"port": 1883,
"username": "mock-user",
"password": "mock-pass",
"protocol": "3.1.1",
},
)
addon_installed.return_value.name = "Mosquitto Test"
@ -208,7 +189,7 @@ async def test_hassio_discovery_webhook(
await hass.async_block_till_done()
assert resp.status == HTTPStatus.OK
assert aioclient_mock.call_count == 1
assert get_discovery_message.call_count == 1
assert mock_mqtt.async_step_hassio.called
mock_mqtt.async_step_hassio.assert_called_with(
HassioServiceInfo(
@ -222,7 +203,7 @@ async def test_hassio_discovery_webhook(
},
name="Mosquitto Test",
slug="mosquitto",
uuid="test",
uuid=uuid,
)
)
@ -271,6 +252,8 @@ async def test_hassio_rediscover(
entry_domain: str,
entry_discovery_keys: dict[str, tuple[DiscoveryKey, ...]],
entry_source: str,
get_addon_discovery_info: AsyncMock,
get_discovery_message: AsyncMock,
) -> None:
"""Test we reinitiate flows when an ignored config entry is removed."""
@ -286,30 +269,21 @@ async def test_hassio_rediscover(
)
entry.add_to_hass(hass)
aioclient_mock.get(
"http://127.0.0.1/discovery/test",
json={
"result": "ok",
"data": {
"service": "mqtt",
"uuid": "test",
"addon": "mosquitto",
"config": {
"broker": "mock-broker",
"port": 1883,
"username": "mock-user",
"password": "mock-pass",
"protocol": "3.1.1",
},
},
get_discovery_message.return_value = Discovery(
addon="mosquitto",
service="mqtt",
uuid=(uuid := uuid4()),
config={
"broker": "mock-broker",
"port": 1883,
"username": "mock-user",
"password": "mock-pass",
"protocol": "3.1.1",
},
)
aioclient_mock.get(
"http://127.0.0.1/discovery", json={"result": "ok", "data": {"discovery": []}}
)
expected_context = {
"discovery_key": DiscoveryKey(domain="hassio", key="test", version=1),
"discovery_key": DiscoveryKey(domain="hassio", key=uuid, version=1),
"source": config_entries.SOURCE_HASSIO,
}

View File

@ -4,7 +4,6 @@ from __future__ import annotations
from typing import Any, Literal
import aiohttp
from aiohttp import hdrs, web
import pytest
@ -16,36 +15,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from tests.test_util.aiohttp import AiohttpClientMocker
async def test_api_ping(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test setup with API ping."""
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"})
assert await hassio_handler.is_connected()
assert aioclient_mock.call_count == 1
async def test_api_ping_error(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test setup with API ping error."""
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "error"})
assert not (await hassio_handler.is_connected())
assert aioclient_mock.call_count == 1
async def test_api_ping_exeption(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test setup with API ping exception."""
aioclient_mock.get("http://127.0.0.1/supervisor/ping", exc=aiohttp.ClientError())
assert not (await hassio_handler.is_connected())
assert aioclient_mock.call_count == 1
async def test_api_info(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
@ -181,26 +150,6 @@ async def test_api_core_info_error(
assert aioclient_mock.call_count == 1
async def test_api_homeassistant_stop(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test setup with API Home Assistant stop."""
aioclient_mock.post("http://127.0.0.1/homeassistant/stop", json={"result": "ok"})
assert await hassio_handler.stop_homeassistant()
assert aioclient_mock.call_count == 1
async def test_api_homeassistant_restart(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test setup with API Home Assistant restart."""
aioclient_mock.post("http://127.0.0.1/homeassistant/restart", json={"result": "ok"})
assert await hassio_handler.restart_homeassistant()
assert aioclient_mock.call_count == 1
async def test_api_core_stats(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
@ -229,34 +178,6 @@ async def test_api_supervisor_stats(
assert aioclient_mock.call_count == 1
async def test_api_discovery_message(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test setup with API discovery message."""
aioclient_mock.get(
"http://127.0.0.1/discovery/test",
json={"result": "ok", "data": {"service": "mqtt"}},
)
data = await hassio_handler.get_discovery_message("test")
assert data["service"] == "mqtt"
assert aioclient_mock.call_count == 1
async def test_api_retrieve_discovery(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test setup with API discovery message."""
aioclient_mock.get(
"http://127.0.0.1/discovery",
json={"result": "ok", "data": {"discovery": [{"service": "mqtt"}]}},
)
data = await hassio_handler.retrieve_discovery_messages()
assert data["discovery"][-1]["service"] == "mqtt"
assert aioclient_mock.call_count == 1
async def test_api_ingress_panels(
hassio_handler: HassIO, aioclient_mock: AiohttpClientMocker
) -> None:
@ -287,8 +208,7 @@ async def test_api_ingress_panels(
@pytest.mark.parametrize(
("api_call", "method", "payload"),
[
("retrieve_discovery_messages", "GET", None),
("refresh_updates", "POST", None),
("get_resolution_info", "GET", None),
("update_diagnostics", "POST", True),
],
)

View File

@ -5,6 +5,7 @@ import os
from typing import Any
from unittest.mock import AsyncMock, patch
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import AddonsStats
import pytest
from voluptuous import Invalid
@ -21,7 +22,6 @@ from homeassistant.components.hassio import (
is_hassio,
)
from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY
from homeassistant.components.hassio.handler import HassioAPIError
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, issue_registry as ir
@ -62,7 +62,6 @@ def mock_all(
) -> 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"})
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/info",
@ -197,7 +196,6 @@ def mock_all(
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={
@ -282,9 +280,9 @@ async def test_setup_api_push_api_data(
assert result
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 20
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 9999
assert "watchdog" not in aioclient_mock.mock_calls[1][2]
assert not aioclient_mock.mock_calls[0][2]["ssl"]
assert aioclient_mock.mock_calls[0][2]["port"] == 9999
assert "watchdog" not in aioclient_mock.mock_calls[0][2]
async def test_setup_api_push_api_data_server_host(
@ -303,9 +301,9 @@ async def test_setup_api_push_api_data_server_host(
assert result
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 20
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 9999
assert not aioclient_mock.mock_calls[1][2]["watchdog"]
assert not aioclient_mock.mock_calls[0][2]["ssl"]
assert aioclient_mock.mock_calls[0][2]["port"] == 9999
assert not aioclient_mock.mock_calls[0][2]["watchdog"]
async def test_setup_api_push_api_data_default(
@ -321,9 +319,9 @@ async def test_setup_api_push_api_data_default(
assert result
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 20
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 8123
refresh_token = aioclient_mock.mock_calls[1][2]["refresh_token"]
assert not aioclient_mock.mock_calls[0][2]["ssl"]
assert aioclient_mock.mock_calls[0][2]["port"] == 8123
refresh_token = aioclient_mock.mock_calls[0][2]["refresh_token"]
hassio_user = await hass.auth.async_get_user(
hass_storage[STORAGE_KEY]["data"]["hassio_user"]
)
@ -402,9 +400,9 @@ async def test_setup_api_existing_hassio_user(
assert result
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 20
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 8123
assert aioclient_mock.mock_calls[1][2]["refresh_token"] == token.token
assert not aioclient_mock.mock_calls[0][2]["ssl"]
assert aioclient_mock.mock_calls[0][2]["port"] == 8123
assert aioclient_mock.mock_calls[0][2]["refresh_token"] == token.token
async def test_setup_core_push_timezone(
@ -421,7 +419,7 @@ async def test_setup_core_push_timezone(
assert result
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 20
assert aioclient_mock.mock_calls[2][2]["timezone"] == "testzone"
assert aioclient_mock.mock_calls[1][2]["timezone"] == "testzone"
with patch("homeassistant.util.dt.set_default_time_zone"):
await hass.config.async_update(time_zone="America/New_York")
@ -455,16 +453,13 @@ async def test_fail_setup_without_environ_var(hass: HomeAssistant) -> None:
async def test_warn_when_cannot_connect(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
supervisor_is_connected: AsyncMock,
) -> None:
"""Fail warn when we cannot connect."""
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.HassIO.is_connected",
return_value=None,
),
):
supervisor_is_connected.side_effect = SupervisorError
with patch.dict(os.environ, MOCK_ENVIRON):
result = await async_setup_component(hass, "hassio", {})
assert result
@ -496,17 +491,13 @@ async def test_service_calls(
aioclient_mock: AiohttpClientMocker,
caplog: pytest.LogCaptureFixture,
supervisor_client: AsyncMock,
addon_installed,
addon_installed: AsyncMock,
supervisor_is_connected: AsyncMock,
issue_registry: ir.IssueRegistry,
) -> None:
"""Call service and check the API calls behind that."""
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.HassIO.is_connected",
return_value=None,
),
):
supervisor_is_connected.side_effect = SupervisorError
with patch.dict(os.environ, MOCK_ENVIRON):
assert await async_setup_component(hass, "hassio", {})
await hass.async_block_till_done()
@ -536,14 +527,14 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 24
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 25
assert aioclient_mock.mock_calls[-1][2] == "test"
await hass.services.async_call("hassio", "host_shutdown", {})
await hass.services.async_call("hassio", "host_reboot", {})
await hass.async_block_till_done()
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 26
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 27
await hass.services.async_call("hassio", "backup_full", {})
await hass.services.async_call(
@ -558,7 +549,7 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 28
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 29
assert aioclient_mock.mock_calls[-1][2] == {
"name": "2021-11-13 03:48:00",
"homeassistant": True,
@ -583,7 +574,7 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 30
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 31
assert aioclient_mock.mock_calls[-1][2] == {
"addons": ["test"],
"folders": ["ssl"],
@ -602,7 +593,7 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 31
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 32
assert aioclient_mock.mock_calls[-1][2] == {
"name": "backup_name",
"location": "backup_share",
@ -618,7 +609,7 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 32
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 33
assert aioclient_mock.mock_calls[-1][2] == {
"name": "2021-11-13 03:48:00",
"location": None,
@ -637,7 +628,7 @@ async def test_service_calls(
)
await hass.async_block_till_done()
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 34
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 35
assert aioclient_mock.mock_calls[-1][2] == {
"name": "2021-11-13 11:48:00",
"location": None,
@ -647,15 +638,11 @@ async def test_service_calls(
async def test_invalid_service_calls(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
supervisor_is_connected: AsyncMock,
) -> None:
"""Call service with invalid input and check that it raises."""
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.HassIO.is_connected",
return_value=None,
),
):
supervisor_is_connected.side_effect = SupervisorError
with patch.dict(os.environ, MOCK_ENVIRON):
assert await async_setup_component(hass, "hassio", {})
await hass.async_block_till_done()
@ -672,6 +659,7 @@ async def test_invalid_service_calls(
async def test_addon_service_call_with_complex_slug(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
supervisor_is_connected: AsyncMock,
) -> None:
"""Addon slugs can have ., - and _, confirm that passes validation."""
supervisor_mock_data = {
@ -691,12 +679,9 @@ async def test_addon_service_call_with_complex_slug(
},
],
}
supervisor_is_connected.side_effect = SupervisorError
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.HassIO.is_connected",
return_value=None,
),
patch(
"homeassistant.components.hassio.HassIO.get_supervisor_info",
return_value=supervisor_mock_data,
@ -724,12 +709,12 @@ async def test_service_calls_core(
await hass.services.async_call("homeassistant", "stop")
await hass.async_block_till_done()
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 5
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 6
await hass.services.async_call("homeassistant", "check_config")
await hass.async_block_till_done()
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 5
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 6
with patch(
"homeassistant.config.async_check_ha_config_file", return_value=None
@ -738,7 +723,7 @@ async def test_service_calls_core(
await hass.async_block_till_done()
assert mock_check_config.called
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 6
assert aioclient_mock.call_count + len(supervisor_client.mock_calls) == 7
@pytest.mark.usefixtures("addon_installed")
@ -923,129 +908,108 @@ async def test_device_registry_calls(
@pytest.mark.usefixtures("addon_installed")
async def test_coordinator_updates(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, supervisor_client: AsyncMock
) -> None:
"""Test coordinator updates."""
await async_setup_component(hass, "homeassistant", {})
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.HassIO.refresh_updates"
) as refresh_updates_mock,
):
with patch.dict(os.environ, MOCK_ENVIRON):
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Initial refresh, no update refresh call
assert refresh_updates_mock.call_count == 0
supervisor_client.refresh_updates.assert_not_called()
with patch(
"homeassistant.components.hassio.HassIO.refresh_updates",
) as refresh_updates_mock:
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done()
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done()
# Scheduled refresh, no update refresh call
assert refresh_updates_mock.call_count == 0
# Scheduled refresh, no update refresh call
supervisor_client.refresh_updates.assert_not_called()
with patch(
"homeassistant.components.hassio.HassIO.refresh_updates",
) as refresh_updates_mock:
await hass.services.async_call(
"homeassistant",
"update_entity",
{
"entity_id": [
"update.home_assistant_core_update",
"update.home_assistant_supervisor_update",
]
},
blocking=True,
)
await hass.services.async_call(
"homeassistant",
"update_entity",
{
"entity_id": [
"update.home_assistant_core_update",
"update.home_assistant_supervisor_update",
]
},
blocking=True,
)
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
assert refresh_updates_mock.call_count == 0
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
assert refresh_updates_mock.call_count == 1
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
supervisor_client.refresh_updates.assert_not_called()
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
supervisor_client.refresh_updates.assert_called_once()
with patch(
"homeassistant.components.hassio.HassIO.refresh_updates",
side_effect=HassioAPIError("Unknown"),
) as refresh_updates_mock:
await hass.services.async_call(
"homeassistant",
"update_entity",
{
"entity_id": [
"update.home_assistant_core_update",
"update.home_assistant_supervisor_update",
]
},
blocking=True,
)
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
assert refresh_updates_mock.call_count == 1
assert "Error on Supervisor API: Unknown" in caplog.text
supervisor_client.refresh_updates.reset_mock()
supervisor_client.refresh_updates.side_effect = SupervisorError("Unknown")
await hass.services.async_call(
"homeassistant",
"update_entity",
{
"entity_id": [
"update.home_assistant_core_update",
"update.home_assistant_supervisor_update",
]
},
blocking=True,
)
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
supervisor_client.refresh_updates.assert_called_once()
assert "Error on Supervisor API: Unknown" in caplog.text
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "addon_installed")
async def test_coordinator_updates_stats_entities_enabled(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
supervisor_client: AsyncMock,
) -> None:
"""Test coordinator updates with stats entities enabled."""
await async_setup_component(hass, "homeassistant", {})
with (
patch.dict(os.environ, MOCK_ENVIRON),
patch(
"homeassistant.components.hassio.HassIO.refresh_updates"
) as refresh_updates_mock,
):
with patch.dict(os.environ, MOCK_ENVIRON):
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Initial refresh without stats
assert refresh_updates_mock.call_count == 0
supervisor_client.refresh_updates.assert_not_called()
# Refresh with stats once we know which ones are needed
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
assert refresh_updates_mock.call_count == 1
with patch(
"homeassistant.components.hassio.HassIO.refresh_updates",
) as refresh_updates_mock:
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done()
assert refresh_updates_mock.call_count == 0
supervisor_client.refresh_updates.assert_called_once()
with patch(
"homeassistant.components.hassio.HassIO.refresh_updates",
) as refresh_updates_mock:
await hass.services.async_call(
"homeassistant",
"update_entity",
{
"entity_id": [
"update.home_assistant_core_update",
"update.home_assistant_supervisor_update",
]
},
blocking=True,
)
assert refresh_updates_mock.call_count == 0
supervisor_client.refresh_updates.reset_mock()
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done()
supervisor_client.refresh_updates.assert_not_called()
await hass.services.async_call(
"homeassistant",
"update_entity",
{
"entity_id": [
"update.home_assistant_core_update",
"update.home_assistant_supervisor_update",
]
},
blocking=True,
)
supervisor_client.refresh_updates.assert_not_called()
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
async_fire_time_changed(
@ -1053,28 +1017,26 @@ async def test_coordinator_updates_stats_entities_enabled(
)
await hass.async_block_till_done()
with patch(
"homeassistant.components.hassio.HassIO.refresh_updates",
side_effect=HassioAPIError("Unknown"),
) as refresh_updates_mock:
await hass.services.async_call(
"homeassistant",
"update_entity",
{
"entity_id": [
"update.home_assistant_core_update",
"update.home_assistant_supervisor_update",
]
},
blocking=True,
)
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
assert refresh_updates_mock.call_count == 1
assert "Error on Supervisor API: Unknown" in caplog.text
supervisor_client.refresh_updates.reset_mock()
supervisor_client.refresh_updates.side_effect = SupervisorError("Unknown")
await hass.services.async_call(
"homeassistant",
"update_entity",
{
"entity_id": [
"update.home_assistant_core_update",
"update.home_assistant_supervisor_update",
]
},
blocking=True,
)
# There is a REQUEST_REFRESH_DELAYs cooldown on the debouncer
async_fire_time_changed(
hass, dt_util.now() + timedelta(seconds=REQUEST_REFRESH_DELAY)
)
await hass.async_block_till_done()
supervisor_client.refresh_updates.assert_called_once()
assert "Error on Supervisor API: Unknown" in caplog.text
@pytest.mark.parametrize(

View File

@ -41,7 +41,6 @@ def mock_all(
def _install_default_mocks(aioclient_mock: AiohttpClientMocker):
"""Install default mocks."""
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"})
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/info",
@ -147,7 +146,6 @@ def _install_default_mocks(aioclient_mock: AiohttpClientMocker):
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={

View File

@ -8,7 +8,7 @@ from aiohasupervisor import SupervisorBadRequestError, SupervisorError
from aiohasupervisor.models import StoreAddonUpdate
import pytest
from homeassistant.components.hassio import DOMAIN, HassioAPIError
from homeassistant.components.hassio import DOMAIN
from homeassistant.components.hassio.const import REQUEST_REFRESH_DELAY
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
@ -32,7 +32,6 @@ def mock_all(
) -> 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"})
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/info",
@ -150,7 +149,6 @@ def mock_all(
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={
@ -239,9 +237,7 @@ async def test_update_addon(hass: HomeAssistant, update_addon: AsyncMock) -> Non
update_addon.assert_called_once_with("test", StoreAddonUpdate(backup=False))
async def test_update_os(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
async def test_update_os(hass: HomeAssistant, supervisor_client: AsyncMock) -> None:
"""Test updating OS update entity."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
config_entry.add_to_hass(hass)
@ -255,22 +251,17 @@ async def test_update_os(
assert result
await hass.async_block_till_done()
aioclient_mock.post(
"http://127.0.0.1/os/update",
json={"result": "ok", "data": {}},
)
supervisor_client.os.update.return_value = None
await hass.services.async_call(
"update",
"install",
{"entity_id": "update.home_assistant_operating_system_update"},
blocking=True,
)
supervisor_client.os.update.assert_called_once()
async def test_update_core(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
async def test_update_core(hass: HomeAssistant, supervisor_client: AsyncMock) -> None:
"""Test updating core update entity."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
config_entry.add_to_hass(hass)
@ -284,21 +275,18 @@ async def test_update_core(
assert result
await hass.async_block_till_done()
aioclient_mock.post(
"http://127.0.0.1/core/update",
json={"result": "ok", "data": {}},
)
supervisor_client.homeassistant.update.return_value = None
await hass.services.async_call(
"update",
"install",
{"entity_id": "update.home_assistant_os_update"},
{"entity_id": "update.home_assistant_core_update"},
blocking=True,
)
supervisor_client.homeassistant.update.assert_called_once()
async def test_update_supervisor(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
"""Test updating supervisor update entity."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
@ -313,17 +301,14 @@ async def test_update_supervisor(
assert result
await hass.async_block_till_done()
aioclient_mock.post(
"http://127.0.0.1/supervisor/update",
json={"result": "ok", "data": {}},
)
supervisor_client.supervisor.update.return_value = None
await hass.services.async_call(
"update",
"install",
{"entity_id": "update.home_assistant_supervisor_update"},
blocking=True,
)
supervisor_client.supervisor.update.assert_called_once()
async def test_update_addon_with_error(
@ -353,7 +338,7 @@ async def test_update_addon_with_error(
async def test_update_os_with_error(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
"""Test updating OS update entity with error."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
@ -367,11 +352,7 @@ async def test_update_os_with_error(
)
await hass.async_block_till_done()
aioclient_mock.post(
"http://127.0.0.1/os/update",
exc=HassioAPIError,
)
supervisor_client.os.update.side_effect = SupervisorError
with pytest.raises(
HomeAssistantError, match=r"^Error updating Home Assistant Operating System:"
):
@ -384,7 +365,7 @@ async def test_update_os_with_error(
async def test_update_supervisor_with_error(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
"""Test updating supervisor update entity with error."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
@ -398,11 +379,7 @@ async def test_update_supervisor_with_error(
)
await hass.async_block_till_done()
aioclient_mock.post(
"http://127.0.0.1/supervisor/update",
exc=HassioAPIError,
)
supervisor_client.supervisor.update.side_effect = SupervisorError
with pytest.raises(
HomeAssistantError, match=r"^Error updating Home Assistant Supervisor:"
):
@ -415,7 +392,7 @@ async def test_update_supervisor_with_error(
async def test_update_core_with_error(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
hass: HomeAssistant, supervisor_client: AsyncMock
) -> None:
"""Test updating core update entity with error."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=DOMAIN)
@ -429,11 +406,7 @@ async def test_update_core_with_error(
)
await hass.async_block_till_done()
aioclient_mock.post(
"http://127.0.0.1/core/update",
exc=HassioAPIError,
)
supervisor_client.homeassistant.update.side_effect = SupervisorError
with pytest.raises(
HomeAssistantError, match=r"^Error updating Home Assistant Core:"
):

View File

@ -1,5 +1,7 @@
"""Test websocket API."""
from unittest.mock import AsyncMock
import pytest
from homeassistant.components.hassio.const import (
@ -23,10 +25,11 @@ from tests.typing import WebSocketGenerator
@pytest.fixture(autouse=True)
def mock_all(aioclient_mock: AiohttpClientMocker) -> None:
def mock_all(
aioclient_mock: AiohttpClientMocker, supervisor_is_connected: 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"})
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/info",

View File

@ -3,7 +3,7 @@
from http import HTTPStatus
from ipaddress import ip_address
import os
from unittest.mock import Mock, mock_open, patch
from unittest.mock import AsyncMock, Mock, mock_open, patch
from aiohttp import web
from aiohttp.web_exceptions import HTTPUnauthorized
@ -34,14 +34,10 @@ BANNED_IPS_WITH_SUPERVISOR = [*BANNED_IPS, SUPERVISOR_IP]
@pytest.fixture(name="hassio_env")
def hassio_env_fixture():
def hassio_env_fixture(supervisor_is_connected: AsyncMock):
"""Fixture to inject hassio env."""
with (
patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}),
patch(
"homeassistant.components.hassio.HassIO.is_connected",
return_value={"result": "ok", "data": {}},
),
patch.dict(os.environ, {"SUPERVISOR_TOKEN": "123456"}),
):
yield

View File

@ -5,13 +5,15 @@ from __future__ import annotations
from collections.abc import Generator
from ipaddress import ip_address
from unittest.mock import AsyncMock, MagicMock, call, patch
from uuid import uuid4
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import Discovery
from matter_server.client.exceptions import CannotConnect, InvalidServerVersion
import pytest
from homeassistant import config_entries
from homeassistant.components.hassio import HassioAPIError, HassioServiceInfo
from homeassistant.components.hassio import HassioServiceInfo
from homeassistant.components.matter.const import ADDON_SLUG, DOMAIN
from homeassistant.components.zeroconf import ZeroconfServiceInfo
from homeassistant.core import HomeAssistant
@ -290,7 +292,19 @@ async def test_zeroconf_discovery_not_onboarded_not_supervisor(
@pytest.mark.parametrize("zeroconf_info", [ZEROCONF_INFO_TCP, ZEROCONF_INFO_UDP])
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_zeroconf_not_onboarded_already_discovered(
hass: HomeAssistant,
supervisor: MagicMock,
@ -328,7 +342,19 @@ async def test_zeroconf_not_onboarded_already_discovered(
@pytest.mark.parametrize("zeroconf_info", [ZEROCONF_INFO_TCP, ZEROCONF_INFO_UDP])
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_zeroconf_not_onboarded_running(
hass: HomeAssistant,
supervisor: MagicMock,
@ -360,7 +386,19 @@ async def test_zeroconf_not_onboarded_running(
@pytest.mark.parametrize("zeroconf_info", [ZEROCONF_INFO_TCP, ZEROCONF_INFO_UDP])
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_zeroconf_not_onboarded_installed(
hass: HomeAssistant,
supervisor: MagicMock,
@ -394,7 +432,19 @@ async def test_zeroconf_not_onboarded_installed(
@pytest.mark.parametrize("zeroconf_info", [ZEROCONF_INFO_TCP, ZEROCONF_INFO_UDP])
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_zeroconf_not_onboarded_not_installed(
hass: HomeAssistant,
supervisor: MagicMock,
@ -431,7 +481,19 @@ async def test_zeroconf_not_onboarded_not_installed(
assert setup_entry.call_count == 1
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_supervisor_discovery(
hass: HomeAssistant,
supervisor: MagicMock,
@ -469,7 +531,19 @@ async def test_supervisor_discovery(
@pytest.mark.parametrize(
("discovery_info", "error"),
[({"config": ADDON_DISCOVERY_INFO}, SupervisorError())],
[
(
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
SupervisorError(),
)
],
)
async def test_supervisor_discovery_addon_info_failed(
hass: HomeAssistant,
@ -502,7 +576,19 @@ async def test_supervisor_discovery_addon_info_failed(
assert result["reason"] == "addon_info_failed"
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_clean_supervisor_discovery_on_user_create(
hass: HomeAssistant,
supervisor: MagicMock,
@ -793,7 +879,19 @@ async def test_not_addon(
assert setup_entry.call_count == 1
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_running(
hass: HomeAssistant,
supervisor: MagicMock,
@ -839,8 +937,15 @@ async def test_addon_running(
),
[
(
{"config": ADDON_DISCOVERY_INFO},
HassioAPIError(),
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
SupervisorError(),
None,
None,
"addon_get_discovery_info_failed",
@ -848,7 +953,14 @@ async def test_addon_running(
False,
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
None,
CannotConnect(Exception("Boom")),
None,
@ -857,7 +969,7 @@ async def test_addon_running(
True,
),
(
None,
[],
None,
None,
None,
@ -866,7 +978,14 @@ async def test_addon_running(
False,
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
None,
None,
SupervisorError(),
@ -925,8 +1044,15 @@ async def test_addon_running_failures(
),
[
(
{"config": ADDON_DISCOVERY_INFO},
HassioAPIError(),
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
SupervisorError(),
None,
None,
"addon_get_discovery_info_failed",
@ -934,7 +1060,14 @@ async def test_addon_running_failures(
False,
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
None,
CannotConnect(Exception("Boom")),
None,
@ -943,7 +1076,7 @@ async def test_addon_running_failures(
True,
),
(
None,
[],
None,
None,
None,
@ -952,7 +1085,14 @@ async def test_addon_running_failures(
False,
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
None,
None,
SupervisorError(),
@ -996,7 +1136,19 @@ async def test_addon_running_failures_zeroconf(
assert result["reason"] == abort_reason
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_running_already_configured(
hass: HomeAssistant,
supervisor: MagicMock,
@ -1034,7 +1186,19 @@ async def test_addon_running_already_configured(
assert setup_entry.call_count == 1
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_installed(
hass: HomeAssistant,
supervisor: MagicMock,
@ -1084,21 +1248,35 @@ async def test_addon_installed(
),
[
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
SupervisorError(),
None,
False,
False,
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
None,
CannotConnect(Exception("Boom")),
True,
True,
),
(
None,
[],
None,
None,
True,
@ -1159,21 +1337,35 @@ async def test_addon_installed_failures(
),
[
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
SupervisorError(),
None,
False,
False,
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
None,
CannotConnect(Exception("Boom")),
True,
True,
),
(
None,
[],
None,
None,
True,
@ -1213,7 +1405,19 @@ async def test_addon_installed_failures_zeroconf(
assert result["reason"] == "addon_start_failed"
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_installed_already_configured(
hass: HomeAssistant,
supervisor: MagicMock,
@ -1259,7 +1463,19 @@ async def test_addon_installed_already_configured(
assert setup_entry.call_count == 1
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_not_installed(
hass: HomeAssistant,
supervisor: MagicMock,
@ -1368,7 +1584,19 @@ async def test_addon_not_installed_failures_zeroconf(
assert result["reason"] == "addon_install_failed"
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_matter_server",
service="matter",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_not_installed_already_configured(
hass: HomeAssistant,
supervisor: MagicMock,

View File

@ -9,6 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import Discovery
import pytest
import voluptuous as vol
@ -528,7 +529,19 @@ async def test_hassio_cannot_connect(
@pytest.mark.usefixtures(
"mqtt_client_mock", "supervisor", "addon_info", "addon_running"
)
@pytest.mark.parametrize("discovery_info", [{"config": ADD_ON_DISCOVERY_INFO.copy()}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_mosquitto",
service="mqtt",
uuid=uuid4(),
config=ADD_ON_DISCOVERY_INFO.copy(),
)
]
],
)
async def test_addon_flow_with_supervisor_addon_running(
hass: HomeAssistant,
mock_try_connection_success: MagicMock,
@ -570,7 +583,19 @@ async def test_addon_flow_with_supervisor_addon_running(
@pytest.mark.usefixtures(
"mqtt_client_mock", "supervisor", "addon_info", "addon_installed", "start_addon"
)
@pytest.mark.parametrize("discovery_info", [{"config": ADD_ON_DISCOVERY_INFO.copy()}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_mosquitto",
service="mqtt",
uuid=uuid4(),
config=ADD_ON_DISCOVERY_INFO.copy(),
)
]
],
)
async def test_addon_flow_with_supervisor_addon_installed(
hass: HomeAssistant,
mock_try_connection_success: MagicMock,
@ -625,7 +650,19 @@ async def test_addon_flow_with_supervisor_addon_installed(
@pytest.mark.usefixtures(
"mqtt_client_mock", "supervisor", "addon_info", "addon_running"
)
@pytest.mark.parametrize("discovery_info", [{"config": ADD_ON_DISCOVERY_INFO.copy()}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_mosquitto",
service="mqtt",
uuid=uuid4(),
config=ADD_ON_DISCOVERY_INFO.copy(),
)
]
],
)
async def test_addon_flow_with_supervisor_addon_running_connection_fails(
hass: HomeAssistant,
mock_try_connection: MagicMock,
@ -780,7 +817,19 @@ async def test_addon_info_error(
"install_addon",
"start_addon",
)
@pytest.mark.parametrize("discovery_info", [{"config": ADD_ON_DISCOVERY_INFO.copy()}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_mosquitto",
service="mqtt",
uuid=uuid4(),
config=ADD_ON_DISCOVERY_INFO.copy(),
)
]
],
)
async def test_addon_flow_with_supervisor_addon_not_installed(
hass: HomeAssistant,
mock_try_connection_success: MagicMock,
@ -1576,7 +1625,19 @@ async def test_step_reauth(
await hass.async_block_till_done()
@pytest.mark.parametrize("discovery_info", [{"config": ADD_ON_DISCOVERY_INFO.copy()}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_mosquitto",
service="mqtt",
uuid=uuid4(),
config=ADD_ON_DISCOVERY_INFO.copy(),
)
]
],
)
@pytest.mark.usefixtures(
"mqtt_client_mock", "mock_reload_after_entry_update", "supervisor", "addon_running"
)
@ -1625,8 +1686,30 @@ async def test_step_hassio_reauth(
@pytest.mark.parametrize(
("discovery_info", "discovery_info_side_effect", "broker"),
[
({"config": ADD_ON_DISCOVERY_INFO.copy()}, AddonError, "core-mosquitto"),
({"config": ADD_ON_DISCOVERY_INFO.copy()}, None, "broker-not-addon"),
(
[
Discovery(
addon="core_mosquitto",
service="mqtt",
uuid=uuid4(),
config=ADD_ON_DISCOVERY_INFO.copy(),
)
],
AddonError,
"core-mosquitto",
),
(
[
Discovery(
addon="core_mosquitto",
service="mqtt",
uuid=uuid4(),
config=ADD_ON_DISCOVERY_INFO.copy(),
)
],
None,
"broker-not-addon",
),
],
)
@pytest.mark.usefixtures(

View File

@ -5,7 +5,7 @@ from collections.abc import AsyncGenerator
from http import HTTPStatus
import os
from typing import Any
from unittest.mock import Mock, patch
from unittest.mock import AsyncMock, Mock, patch
import pytest
@ -69,7 +69,9 @@ async def no_rpi_fixture(
@pytest.fixture(name="mock_supervisor")
async def mock_supervisor_fixture(
aioclient_mock: AiohttpClientMocker, store_info
aioclient_mock: AiohttpClientMocker,
store_info: AsyncMock,
supervisor_is_connected: AsyncMock,
) -> AsyncGenerator[None]:
"""Mock supervisor."""
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
@ -99,10 +101,6 @@ async def mock_supervisor_fixture(
)
with (
patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}),
patch(
"homeassistant.components.hassio.HassIO.is_connected",
return_value=True,
),
patch(
"homeassistant.components.hassio.HassIO.get_info",
return_value={},

View File

@ -6,9 +6,10 @@ from copy import copy
from ipaddress import ip_address
from typing import Any
from unittest.mock import AsyncMock, MagicMock, call, patch
from uuid import uuid4
from aiohasupervisor import SupervisorError
from aiohasupervisor.models import AddonsOptions
from aiohasupervisor.models import AddonsOptions, Discovery
import aiohttp
import pytest
from serial.tools.list_ports_common import ListPortInfo
@ -16,7 +17,7 @@ from zwave_js_server.version import VersionInfo
from homeassistant import config_entries
from homeassistant.components import usb
from homeassistant.components.hassio import HassioAPIError, HassioServiceInfo
from homeassistant.components.hassio import HassioServiceInfo
from homeassistant.components.zeroconf import ZeroconfServiceInfo
from homeassistant.components.zwave_js.config_flow import SERVER_VERSION_TIMEOUT, TITLE
from homeassistant.components.zwave_js.const import ADDON_SLUG, DOMAIN
@ -555,7 +556,19 @@ async def test_abort_hassio_discovery_for_other_addon(
assert result2["reason"] == "not_zwave_js_addon"
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_usb_discovery(
hass: HomeAssistant,
supervisor,
@ -653,7 +666,19 @@ async def test_usb_discovery(
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_usb_discovery_addon_not_running(
hass: HomeAssistant,
supervisor,
@ -1090,7 +1115,19 @@ async def test_not_addon(hass: HomeAssistant, supervisor) -> None:
assert len(mock_setup_entry.mock_calls) == 1
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_running(
hass: HomeAssistant,
supervisor,
@ -1156,28 +1193,49 @@ async def test_addon_running(
),
[
(
{"config": ADDON_DISCOVERY_INFO},
HassioAPIError(),
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
SupervisorError(),
None,
None,
"addon_get_discovery_info_failed",
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
None,
TimeoutError,
None,
"cannot_connect",
),
(
None,
[],
None,
None,
None,
"addon_get_discovery_info_failed",
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
None,
None,
SupervisorError(),
@ -1212,7 +1270,19 @@ async def test_addon_running_failures(
assert result["reason"] == abort_reason
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_running_already_configured(
hass: HomeAssistant,
supervisor,
@ -1271,7 +1341,19 @@ async def test_addon_running_already_configured(
assert entry.data["lr_s2_authenticated_key"] == "new321"
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_installed(
hass: HomeAssistant,
supervisor,
@ -1363,7 +1445,17 @@ async def test_addon_installed(
@pytest.mark.parametrize(
("discovery_info", "start_addon_side_effect"),
[({"config": ADDON_DISCOVERY_INFO}, SupervisorError())],
[
(
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
),
SupervisorError(),
)
],
)
async def test_addon_installed_start_failure(
hass: HomeAssistant,
@ -1434,11 +1526,18 @@ async def test_addon_installed_start_failure(
("discovery_info", "server_version_side_effect"),
[
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
TimeoutError,
),
(
None,
[],
None,
),
],
@ -1510,7 +1609,19 @@ async def test_addon_installed_failures(
@pytest.mark.parametrize(
("set_addon_options_side_effect", "discovery_info"),
[(SupervisorError(), {"config": ADDON_DISCOVERY_INFO})],
[
(
SupervisorError(),
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
)
],
)
async def test_addon_installed_set_options_failure(
hass: HomeAssistant,
@ -1571,7 +1682,19 @@ async def test_addon_installed_set_options_failure(
assert start_addon.call_count == 0
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_installed_already_configured(
hass: HomeAssistant,
supervisor,
@ -1662,7 +1785,19 @@ async def test_addon_installed_already_configured(
assert entry.data["lr_s2_authenticated_key"] == "new321"
@pytest.mark.parametrize("discovery_info", [{"config": ADDON_DISCOVERY_INFO}])
@pytest.mark.parametrize(
"discovery_info",
[
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
]
],
)
async def test_addon_not_installed(
hass: HomeAssistant,
supervisor,
@ -1887,7 +2022,14 @@ async def test_options_not_addon(
),
[
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
{},
{
"device": "/test",
@ -1913,7 +2055,14 @@ async def test_options_not_addon(
0,
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
{"use_addon": True},
{
"device": "/test",
@ -2033,7 +2182,14 @@ async def test_options_addon_running(
("discovery_info", "entry_data", "old_addon_options", "new_addon_options"),
[
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
{},
{
"device": "/test",
@ -2160,7 +2316,14 @@ async def different_device_server_version(*args):
),
[
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
{},
{
"device": "/test",
@ -2189,7 +2352,14 @@ async def different_device_server_version(*args):
different_device_server_version,
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
{},
{
"device": "/test",
@ -2318,7 +2488,14 @@ async def test_options_different_device(
),
[
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
{},
{
"device": "/test",
@ -2347,7 +2524,14 @@ async def test_options_different_device(
[SupervisorError(), None],
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
{},
{
"device": "/test",
@ -2477,7 +2661,14 @@ async def test_options_addon_restart_failed(
),
[
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
{},
{
"device": "/test",
@ -2570,7 +2761,14 @@ async def test_options_addon_running_server_info_failure(
),
[
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
{},
{
"device": "/test",
@ -2596,7 +2794,14 @@ async def test_options_addon_running_server_info_failure(
0,
),
(
{"config": ADDON_DISCOVERY_INFO},
[
Discovery(
addon="core_zwave_js",
service="zwave_js",
uuid=uuid4(),
config=ADDON_DISCOVERY_INFO,
)
],
{"use_addon": True},
{
"device": "/test",