diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index e352f8d0cb3..0c70e83cb79 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -13,6 +13,7 @@ import struct from typing import Any, NamedTuple from aiohasupervisor import SupervisorError +from aiohasupervisor.models import GreenOptions, YellowOptions # noqa: F401 import voluptuous as vol from homeassistant.auth.const import GROUP_ID_ADMIN @@ -123,11 +124,6 @@ from .discovery import async_setup_discovery_view from .handler import ( # noqa: F401 HassIO, HassioAPIError, - async_create_backup, - async_get_green_settings, - async_get_yellow_settings, - async_set_green_settings, - async_set_yellow_settings, async_update_diagnostics, get_supervisor_client, ) diff --git a/homeassistant/components/hassio/addon_manager.py b/homeassistant/components/hassio/addon_manager.py index db81e17e48d..b4dc294babb 100644 --- a/homeassistant/components/hassio/addon_manager.py +++ b/homeassistant/components/hassio/addon_manager.py @@ -15,13 +15,14 @@ from aiohasupervisor.models import ( AddonsOptions, AddonState as SupervisorAddonState, InstalledAddonComplete, + PartialBackupOptions, StoreAddonUpdate, ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError -from .handler import HassioAPIError, async_create_backup, get_supervisor_client +from .handler import HassioAPIError, get_supervisor_client type _FuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], Awaitable[_R]] type _ReturnFuncType[_T, **_P, _R] = Callable[ @@ -261,17 +262,18 @@ class AddonManager: """Stop the managed add-on.""" await self._supervisor_client.addons.stop_addon(self.addon_slug) - @api_error("Failed to create a backup of the {addon_name} add-on") + @api_error( + "Failed to create a backup of the {addon_name} add-on", + expected_error_type=SupervisorError, + ) async def async_create_backup(self) -> None: """Create a partial backup of the managed add-on.""" addon_info = await self.async_get_addon_info() name = f"addon_{self.addon_slug}_{addon_info.version}" self._logger.debug("Creating backup: %s", name) - await async_create_backup( - self._hass, - {"name": name, "addons": [self.addon_slug]}, - partial=True, + await self._supervisor_client.backups.partial_backup( + PartialBackupOptions(name=name, addons={self.addon_slug}) ) async def async_configure_addon( diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index 7aec0aa7a61..245b298a539 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -10,6 +10,7 @@ import os from typing import Any from aiohasupervisor import SupervisorClient +from aiohasupervisor.models import SupervisorOptions import aiohttp from yarl import URL @@ -22,7 +23,6 @@ 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_MESSAGE, ATTR_RESULT, DATA_COMPONENT, X_HASS_SOURCE @@ -66,73 +66,6 @@ def api_data[**_P]( return _wrapper -@bind_hass -async def async_update_diagnostics(hass: HomeAssistant, diagnostics: bool) -> bool: - """Update Supervisor diagnostics toggle. - - The caller of the function should handle HassioAPIError. - """ - hassio = hass.data[DATA_COMPONENT] - return await hassio.update_diagnostics(diagnostics) - - -@bind_hass -@api_data -async def async_create_backup( - hass: HomeAssistant, payload: dict, partial: bool = False -) -> dict: - """Create a full or partial backup. - - The caller of the function should handle HassioAPIError. - """ - hassio = hass.data[DATA_COMPONENT] - backup_type = "partial" if partial else "full" - command = f"/backups/new/{backup_type}" - return await hassio.send_command(command, payload=payload, timeout=None) - - -@api_data -async def async_get_green_settings(hass: HomeAssistant) -> dict[str, bool]: - """Return settings specific to Home Assistant Green.""" - hassio = hass.data[DATA_COMPONENT] - return await hassio.send_command("/os/boards/green", method="get") - - -@api_data -async def async_set_green_settings( - hass: HomeAssistant, settings: dict[str, bool] -) -> dict: - """Set settings specific to Home Assistant Green. - - Returns an empty dict. - """ - hassio = hass.data[DATA_COMPONENT] - return await hassio.send_command( - "/os/boards/green", method="post", payload=settings - ) - - -@api_data -async def async_get_yellow_settings(hass: HomeAssistant) -> dict[str, bool]: - """Return settings specific to Home Assistant Yellow.""" - hassio = hass.data[DATA_COMPONENT] - return await hassio.send_command("/os/boards/yellow", method="get") - - -@api_data -async def async_set_yellow_settings( - hass: HomeAssistant, settings: dict[str, bool] -) -> dict: - """Set settings specific to Home Assistant Yellow. - - Returns an empty dict. - """ - hassio = hass.data[DATA_COMPONENT] - return await hassio.send_command( - "/os/boards/yellow", method="post", payload=settings - ) - - class HassIO: """Small API wrapper for Hass.io.""" @@ -257,16 +190,6 @@ class HassIO: "/supervisor/options", payload={"timezone": timezone, "country": country} ) - @_api_bool - def update_diagnostics(self, diagnostics: bool) -> Coroutine: - """Update Supervisor diagnostics setting. - - This method returns a coroutine. - """ - return self.send_command( - "/supervisor/options", payload={"diagnostics": diagnostics} - ) - async def send_command( self, command: str, @@ -341,3 +264,13 @@ def get_supervisor_client(hass: HomeAssistant) -> SupervisorClient: os.environ.get("SUPERVISOR_TOKEN", ""), session=hassio.websession, ) + + +async def async_update_diagnostics(hass: HomeAssistant, diagnostics: bool) -> None: + """Update Supervisor diagnostics toggle. + + The caller of the function should handle SupervisorError. + """ + await get_supervisor_client(hass).supervisor.set_options( + SupervisorOptions(diagnostics=diagnostics) + ) diff --git a/homeassistant/components/homeassistant_green/config_flow.py b/homeassistant/components/homeassistant_green/config_flow.py index c9aed577365..ca03c213db7 100644 --- a/homeassistant/components/homeassistant_green/config_flow.py +++ b/homeassistant/components/homeassistant_green/config_flow.py @@ -6,13 +6,12 @@ import asyncio import logging from typing import Any -import aiohttp import voluptuous as vol from homeassistant.components.hassio import ( - HassioAPIError, - async_get_green_settings, - async_set_green_settings, + GreenOptions, + SupervisorError, + get_supervisor_client, ) from homeassistant.config_entries import ( ConfigEntry, @@ -20,7 +19,7 @@ from homeassistant.config_entries import ( ConfigFlowResult, OptionsFlow, ) -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, async_get_hass, callback from homeassistant.helpers import selector from homeassistant.helpers.hassio import is_hassio @@ -49,7 +48,7 @@ class HomeAssistantGreenConfigFlow(ConfigFlow, domain=DOMAIN): config_entry: ConfigEntry, ) -> HomeAssistantGreenOptionsFlow: """Return the options flow.""" - return HomeAssistantGreenOptionsFlow() + return HomeAssistantGreenOptionsFlow(async_get_hass()) async def async_step_system( self, data: dict[str, Any] | None = None @@ -63,6 +62,11 @@ class HomeAssistantGreenOptionsFlow(OptionsFlow): _hw_settings: dict[str, bool] | None = None + def __init__(self, hass: HomeAssistant, *args: Any, **kwargs: Any) -> None: + """Instantiate options flow.""" + super().__init__(*args, **kwargs) + self._supervisor_client = get_supervisor_client(hass) + async def async_step_init( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: @@ -76,27 +80,27 @@ class HomeAssistantGreenOptionsFlow(OptionsFlow): self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle hardware settings.""" - if user_input is not None: if self._hw_settings == user_input: return self.async_create_entry(data={}) try: async with asyncio.timeout(10): - await async_set_green_settings(self.hass, user_input) - except (aiohttp.ClientError, TimeoutError, HassioAPIError) as err: + await self._supervisor_client.os.set_green_options( + GreenOptions.from_dict(user_input) + ) + except (TimeoutError, SupervisorError) as err: _LOGGER.warning("Failed to write hardware settings", exc_info=err) return self.async_abort(reason="write_hw_settings_error") return self.async_create_entry(data={}) try: async with asyncio.timeout(10): - self._hw_settings: dict[str, bool] = await async_get_green_settings( - self.hass - ) - except (aiohttp.ClientError, TimeoutError, HassioAPIError) as err: + green_info = await self._supervisor_client.os.green_info() + except (TimeoutError, SupervisorError) as err: _LOGGER.warning("Failed to read hardware settings", exc_info=err) return self.async_abort(reason="read_hw_settings_error") + self._hw_settings: dict[str, bool] = green_info.to_dict() schema = self.add_suggested_values_to_schema( STEP_HW_SETTINGS_SCHEMA, self._hw_settings ) diff --git a/homeassistant/components/homeassistant_yellow/config_flow.py b/homeassistant/components/homeassistant_yellow/config_flow.py index 821ba48eee7..8242a5991d5 100644 --- a/homeassistant/components/homeassistant_yellow/config_flow.py +++ b/homeassistant/components/homeassistant_yellow/config_flow.py @@ -7,13 +7,11 @@ import asyncio import logging from typing import TYPE_CHECKING, Any, Protocol, final -import aiohttp import voluptuous as vol from homeassistant.components.hassio import ( - HassioAPIError, - async_get_yellow_settings, - async_set_yellow_settings, + SupervisorError, + YellowOptions, get_supervisor_client, ) from homeassistant.components.homeassistant_hardware.firmware_config_flow import ( @@ -222,21 +220,22 @@ class BaseHomeAssistantYellowOptionsFlow(OptionsFlow, ABC): return self.async_create_entry(data={}) try: async with asyncio.timeout(10): - await async_set_yellow_settings(self.hass, user_input) - except (aiohttp.ClientError, TimeoutError, HassioAPIError) as err: + await self._supervisor_client.os.set_yellow_options( + YellowOptions.from_dict(user_input) + ) + except (TimeoutError, SupervisorError) as err: _LOGGER.warning("Failed to write hardware settings", exc_info=err) return self.async_abort(reason="write_hw_settings_error") return await self.async_step_reboot_menu() try: async with asyncio.timeout(10): - self._hw_settings: dict[str, bool] = await async_get_yellow_settings( - self.hass - ) - except (aiohttp.ClientError, TimeoutError, HassioAPIError) as err: + yellow_info = await self._supervisor_client.os.yellow_info() + except (TimeoutError, SupervisorError) as err: _LOGGER.warning("Failed to read hardware settings", exc_info=err) return self.async_abort(reason="read_hw_settings_error") + self._hw_settings: dict[str, bool] = yellow_info.to_dict() schema = self.add_suggested_values_to_schema( STEP_HW_SETTINGS_SCHEMA, self._hw_settings ) diff --git a/tests/components/conftest.py b/tests/components/conftest.py index eddd4f4b2a8..a35d86ec819 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -14,11 +14,13 @@ from unittest.mock import AsyncMock, MagicMock, patch from aiohasupervisor.models import ( Discovery, + GreenInfo, JobsInfo, Repository, ResolutionInfo, StoreAddon, StoreInfo, + YellowInfo, ) import pytest import voluptuous as vol @@ -430,11 +432,9 @@ def uninstall_addon_fixture(supervisor_client: AsyncMock) -> AsyncMock: @pytest.fixture(name="create_backup") -def create_backup_fixture() -> Generator[AsyncMock]: +def create_backup_fixture(supervisor_client: AsyncMock) -> AsyncMock: """Mock create backup.""" - from .hassio.common import mock_create_backup # noqa: PLC0415 - - yield from mock_create_backup() + return supervisor_client.backups.partial_backup @pytest.fixture(name="update_addon") @@ -517,6 +517,24 @@ def jobs_info_fixture(supervisor_client: AsyncMock) -> AsyncMock: return supervisor_client.jobs.info +@pytest.fixture(name="os_yellow_info") +def os_yellow_info_fixture(supervisor_client: AsyncMock) -> AsyncMock: + """Mock yellow info API from supervisor OS.""" + supervisor_client.os.yellow_info.return_value = YellowInfo( + disk_led=True, heartbeat_led=True, power_led=True + ) + return supervisor_client.os.yellow_info + + +@pytest.fixture(name="os_green_info") +def os_green_info_fixture(supervisor_client: AsyncMock) -> AsyncMock: + """Mock green info API from supervisor OS.""" + supervisor_client.os.green_info.return_value = GreenInfo( + activity_led=True, power_led=True, system_health_led=True + ) + return supervisor_client.os.green_info + + @pytest.fixture(name="supervisor_client") def supervisor_client() -> Generator[AsyncMock]: """Mock the supervisor client.""" diff --git a/tests/components/hassio/common.py b/tests/components/hassio/common.py index 5cf7e80b191..e3310e1d664 100644 --- a/tests/components/hassio/common.py +++ b/tests/components/hassio/common.py @@ -2,12 +2,11 @@ from __future__ import annotations -from collections.abc import Generator from dataclasses import fields import logging from types import MethodType from typing import Any -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import AsyncMock, Mock from aiohasupervisor.models import ( AddonsOptions, @@ -197,14 +196,6 @@ def mock_set_addon_options_side_effect(addon_options: dict[str, Any]) -> Any | N return set_addon_options -def mock_create_backup() -> Generator[AsyncMock]: - """Mock create backup.""" - with patch( - "homeassistant.components.hassio.addon_manager.async_create_backup" - ) as create_backup: - yield create_backup - - def mock_addon_stats(supervisor_client: AsyncMock) -> AsyncMock: """Mock addon stats.""" supervisor_client.addons.addon_stats.return_value = addon_stats = Mock( diff --git a/tests/components/hassio/test_addon_manager.py b/tests/components/hassio/test_addon_manager.py index 3d4644fbfd9..945703a648b 100644 --- a/tests/components/hassio/test_addon_manager.py +++ b/tests/components/hassio/test_addon_manager.py @@ -8,7 +8,7 @@ from unittest.mock import AsyncMock, call from uuid import uuid4 from aiohasupervisor import SupervisorError -from aiohasupervisor.models import AddonsOptions, Discovery +from aiohasupervisor.models import AddonsOptions, Discovery, PartialBackupOptions import pytest from homeassistant.components.hassio.addon_manager import ( @@ -17,7 +17,6 @@ from homeassistant.components.hassio.addon_manager import ( AddonManager, AddonState, ) -from homeassistant.components.hassio.handler import HassioAPIError from homeassistant.core import HomeAssistant @@ -513,7 +512,7 @@ async def test_update_addon( assert addon_info.call_count == 2 assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True + PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"}) ) assert update_addon.call_count == 1 @@ -555,7 +554,7 @@ async def test_update_addon_error( assert addon_info.call_count == 2 assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True + PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"}) ) assert update_addon.call_count == 1 @@ -593,7 +592,7 @@ async def test_schedule_update_addon( assert addon_info.call_count == 3 assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True + PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"}) ) assert update_addon.call_count == 1 @@ -615,7 +614,7 @@ async def test_schedule_update_addon( ), [ ( - HassioAPIError("Boom"), + SupervisorError("Boom"), 1, None, 0, @@ -665,7 +664,7 @@ async def test_schedule_update_addon_error( ), [ ( - HassioAPIError("Boom"), + SupervisorError("Boom"), 1, None, 0, @@ -717,7 +716,7 @@ async def test_create_backup( assert addon_info.call_count == 1 assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True + PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"}) ) @@ -729,7 +728,7 @@ async def test_create_backup_error( create_backup: AsyncMock, ) -> None: """Test creating a backup of the addon raises error.""" - create_backup.side_effect = HassioAPIError("Boom") + create_backup.side_effect = SupervisorError("Boom") with pytest.raises(AddonError) as err: await addon_manager.async_create_backup() @@ -739,7 +738,7 @@ async def test_create_backup_error( assert addon_info.call_count == 1 assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True + PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"}) ) diff --git a/tests/components/hassio/test_handler.py b/tests/components/hassio/test_handler.py index e6375171dab..fc024dfcc44 100644 --- a/tests/components/hassio/test_handler.py +++ b/tests/components/hassio/test_handler.py @@ -7,7 +7,6 @@ from typing import Any, Literal from aiohttp import hdrs, web import pytest -from homeassistant.components.hassio import handler from homeassistant.components.hassio.handler import HassIO, HassioAPIError from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -209,7 +208,6 @@ async def test_api_ingress_panels( ("api_call", "method", "payload"), [ ("get_network_info", "GET", None), - ("update_diagnostics", "POST", True), ], ) @pytest.mark.usefixtures("socket_enabled") @@ -257,90 +255,6 @@ async def test_api_headers( assert received_request.headers[hdrs.CONTENT_TYPE] == "application/octet-stream" -@pytest.mark.usefixtures("hassio_stubs") -async def test_api_get_green_settings( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test setup with API ping.""" - aioclient_mock.get( - "http://127.0.0.1/os/boards/green", - json={ - "result": "ok", - "data": { - "activity_led": True, - "power_led": True, - "system_health_led": True, - }, - }, - ) - - assert await handler.async_get_green_settings(hass) == { - "activity_led": True, - "power_led": True, - "system_health_led": True, - } - assert aioclient_mock.call_count == 1 - - -@pytest.mark.usefixtures("hassio_stubs") -async def test_api_set_green_settings( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test setup with API ping.""" - aioclient_mock.post( - "http://127.0.0.1/os/boards/green", - json={"result": "ok", "data": {}}, - ) - - assert ( - await handler.async_set_green_settings( - hass, {"activity_led": True, "power_led": True, "system_health_led": True} - ) - == {} - ) - assert aioclient_mock.call_count == 1 - - -@pytest.mark.usefixtures("hassio_stubs") -async def test_api_get_yellow_settings( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test setup with API ping.""" - aioclient_mock.get( - "http://127.0.0.1/os/boards/yellow", - json={ - "result": "ok", - "data": {"disk_led": True, "heartbeat_led": True, "power_led": True}, - }, - ) - - assert await handler.async_get_yellow_settings(hass) == { - "disk_led": True, - "heartbeat_led": True, - "power_led": True, - } - assert aioclient_mock.call_count == 1 - - -@pytest.mark.usefixtures("hassio_stubs") -async def test_api_set_yellow_settings( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker -) -> None: - """Test setup with API ping.""" - aioclient_mock.post( - "http://127.0.0.1/os/boards/yellow", - json={"result": "ok", "data": {}}, - ) - - assert ( - await handler.async_set_yellow_settings( - hass, {"disk_led": True, "heartbeat_led": True, "power_led": True} - ) - == {} - ) - assert aioclient_mock.call_count == 1 - - @pytest.mark.usefixtures("hassio_stubs") async def test_send_command_invalid_command(hass: HomeAssistant) -> None: """Test send command fails when command is invalid.""" diff --git a/tests/components/homeassistant_green/test_config_flow.py b/tests/components/homeassistant_green/test_config_flow.py index 5fa71d9a091..abdde59cf0c 100644 --- a/tests/components/homeassistant_green/test_config_flow.py +++ b/tests/components/homeassistant_green/test_config_flow.py @@ -1,7 +1,10 @@ """Test the Home Assistant Green config flow.""" -from unittest.mock import patch +from collections.abc import Generator +from unittest.mock import AsyncMock, patch +from aiohasupervisor import SupervisorError +from aiohasupervisor.models import GreenOptions import pytest from homeassistant.components.hassio import DOMAIN as HASSIO_DOMAIN @@ -13,27 +16,20 @@ from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, MockModule, mock_integration -@pytest.fixture(name="get_green_settings") -def mock_get_green_settings(): - """Mock getting green settings.""" +@pytest.fixture(autouse=True) +def mock_get_supervisor_client(supervisor_client: AsyncMock) -> Generator[None]: + """Mock get_supervisor_client method.""" with patch( - "homeassistant.components.homeassistant_green.config_flow.async_get_green_settings", - return_value={ - "activity_led": True, - "power_led": True, - "system_health_led": True, - }, - ) as get_green_settings: - yield get_green_settings + "homeassistant.components.homeassistant_green.config_flow.get_supervisor_client", + return_value=supervisor_client, + ): + yield @pytest.fixture(name="set_green_settings") -def mock_set_green_settings(): +def mock_set_green_settings(supervisor_client: AsyncMock) -> Generator[AsyncMock]: """Mock setting green settings.""" - with patch( - "homeassistant.components.homeassistant_green.config_flow.async_set_green_settings", - ) as set_green_settings: - yield set_green_settings + return supervisor_client.os.set_green_options async def test_config_flow(hass: HomeAssistant) -> None: @@ -88,6 +84,7 @@ async def test_config_flow_single_entry(hass: HomeAssistant) -> None: mock_setup_entry.assert_not_called() +@pytest.mark.usefixtures("supervisor_client") async def test_option_flow_non_hassio( hass: HomeAssistant, ) -> None: @@ -113,10 +110,10 @@ async def test_option_flow_non_hassio( assert result["reason"] == "not_hassio" +@pytest.mark.usefixtures("os_green_info") async def test_option_flow_led_settings( hass: HomeAssistant, - get_green_settings, - set_green_settings, + set_green_settings: AsyncMock, ) -> None: """Test updating LED settings.""" mock_integration(hass, MockModule("hassio")) @@ -141,14 +138,14 @@ async def test_option_flow_led_settings( ) assert result["type"] is FlowResultType.CREATE_ENTRY set_green_settings.assert_called_once_with( - hass, {"activity_led": False, "power_led": False, "system_health_led": False} + GreenOptions(activity_led=False, power_led=False, system_health_led=False) ) +@pytest.mark.usefixtures("os_green_info") async def test_option_flow_led_settings_unchanged( hass: HomeAssistant, - get_green_settings, - set_green_settings, + set_green_settings: AsyncMock, ) -> None: """Test updating LED settings.""" mock_integration(hass, MockModule("hassio")) @@ -175,7 +172,10 @@ async def test_option_flow_led_settings_unchanged( set_green_settings.assert_not_called() -async def test_option_flow_led_settings_fail_1(hass: HomeAssistant) -> None: +@pytest.mark.parametrize("exc", [TimeoutError, SupervisorError]) +async def test_option_flow_led_settings_fail_1( + hass: HomeAssistant, os_green_info: AsyncMock, exc: type[Exception] +) -> None: """Test updating LED settings.""" mock_integration(hass, MockModule("hassio")) await async_setup_component(hass, HASSIO_DOMAIN, {}) @@ -189,18 +189,17 @@ async def test_option_flow_led_settings_fail_1(hass: HomeAssistant) -> None: ) config_entry.add_to_hass(hass) - with patch( - "homeassistant.components.homeassistant_green.config_flow.async_get_green_settings", - side_effect=TimeoutError, - ): - result = await hass.config_entries.options.async_init(config_entry.entry_id) + os_green_info.side_effect = exc + result = await hass.config_entries.options.async_init(config_entry.entry_id) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "read_hw_settings_error" +@pytest.mark.usefixtures("os_green_info") +@pytest.mark.parametrize("exc", [TimeoutError, SupervisorError]) async def test_option_flow_led_settings_fail_2( - hass: HomeAssistant, get_green_settings + hass: HomeAssistant, set_green_settings: AsyncMock, exc: type[Exception] ) -> None: """Test updating LED settings.""" mock_integration(hass, MockModule("hassio")) @@ -219,13 +218,10 @@ async def test_option_flow_led_settings_fail_2( assert result["type"] is FlowResultType.FORM assert result["step_id"] == "hardware_settings" - with patch( - "homeassistant.components.homeassistant_green.config_flow.async_set_green_settings", - side_effect=TimeoutError, - ): - result = await hass.config_entries.options.async_configure( - result["flow_id"], - {"activity_led": False, "power_led": False, "system_health_led": False}, - ) + set_green_settings.side_effect = exc + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"activity_led": False, "power_led": False, "system_health_led": False}, + ) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "write_hw_settings_error" diff --git a/tests/components/homeassistant_yellow/test_config_flow.py b/tests/components/homeassistant_yellow/test_config_flow.py index 0cb1b2ab3f4..e48497df714 100644 --- a/tests/components/homeassistant_yellow/test_config_flow.py +++ b/tests/components/homeassistant_yellow/test_config_flow.py @@ -3,6 +3,8 @@ from collections.abc import Generator from unittest.mock import AsyncMock, Mock, call, patch +from aiohasupervisor import SupervisorError +from aiohasupervisor.models import YellowOptions import pytest from homeassistant.components.hassio import ( @@ -51,23 +53,10 @@ def mock_get_supervisor_client(supervisor_client: AsyncMock) -> Generator[None]: yield -@pytest.fixture(name="get_yellow_settings") -def mock_get_yellow_settings(): - """Mock getting yellow settings.""" - with patch( - "homeassistant.components.homeassistant_yellow.config_flow.async_get_yellow_settings", - return_value={"disk_led": True, "heartbeat_led": True, "power_led": True}, - ) as get_yellow_settings: - yield get_yellow_settings - - @pytest.fixture(name="set_yellow_settings") -def mock_set_yellow_settings(): +def mock_set_yellow_settings(supervisor_client: AsyncMock) -> Generator[AsyncMock]: """Mock setting yellow settings.""" - with patch( - "homeassistant.components.homeassistant_yellow.config_flow.async_set_yellow_settings", - ) as set_yellow_settings: - yield set_yellow_settings + return supervisor_client.os.set_yellow_options @pytest.fixture(name="reboot_host") @@ -156,9 +145,9 @@ async def test_config_flow_single_entry(hass: HomeAssistant) -> None: ("reboot_menu_choice", "reboot_calls"), [("reboot_now", 1), ("reboot_later", 0)], ) +@pytest.mark.usefixtures("os_yellow_info") async def test_option_flow_led_settings( hass: HomeAssistant, - get_yellow_settings: AsyncMock, set_yellow_settings: AsyncMock, reboot_host: AsyncMock, reboot_menu_choice: str, @@ -196,7 +185,7 @@ async def test_option_flow_led_settings( assert result["type"] is FlowResultType.MENU assert result["step_id"] == "reboot_menu" set_yellow_settings.assert_called_once_with( - hass, {"disk_led": False, "heartbeat_led": False, "power_led": False} + YellowOptions(disk_led=False, heartbeat_led=False, power_led=False) ) result = await hass.config_entries.options.async_configure( @@ -207,10 +196,10 @@ async def test_option_flow_led_settings( assert reboot_host.call_count == reboot_calls +@pytest.mark.usefixtures("os_yellow_info") async def test_option_flow_led_settings_unchanged( hass: HomeAssistant, - get_yellow_settings, - set_yellow_settings, + set_yellow_settings: AsyncMock, ) -> None: """Test updating LED settings.""" mock_integration(hass, MockModule("hassio")) @@ -245,7 +234,10 @@ async def test_option_flow_led_settings_unchanged( set_yellow_settings.assert_not_called() -async def test_option_flow_led_settings_fail_1(hass: HomeAssistant) -> None: +@pytest.mark.parametrize("exc", [SupervisorError, TimeoutError]) +async def test_option_flow_led_settings_fail_1( + hass: HomeAssistant, os_yellow_info: AsyncMock, exc: type[Exception] +) -> None: """Test updating LED settings.""" mock_integration(hass, MockModule("hassio")) await async_setup_component(hass, HASSIO_DOMAIN, {}) @@ -265,20 +257,19 @@ async def test_option_flow_led_settings_fail_1(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.MENU assert result["step_id"] == "main_menu" - with patch( - "homeassistant.components.homeassistant_yellow.config_flow.async_get_yellow_settings", - side_effect=TimeoutError, - ): - result = await hass.config_entries.options.async_configure( - result["flow_id"], - {"next_step_id": "hardware_settings"}, - ) + os_yellow_info.side_effect = exc + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"next_step_id": "hardware_settings"}, + ) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "read_hw_settings_error" +@pytest.mark.parametrize("exc", [SupervisorError, TimeoutError]) +@pytest.mark.usefixtures("os_yellow_info") async def test_option_flow_led_settings_fail_2( - hass: HomeAssistant, get_yellow_settings + hass: HomeAssistant, set_yellow_settings: AsyncMock, exc: type[Exception] ) -> None: """Test updating LED settings.""" mock_integration(hass, MockModule("hassio")) @@ -305,14 +296,11 @@ async def test_option_flow_led_settings_fail_2( ) assert result["type"] is FlowResultType.FORM - with patch( - "homeassistant.components.homeassistant_yellow.config_flow.async_set_yellow_settings", - side_effect=TimeoutError, - ): - result = await hass.config_entries.options.async_configure( - result["flow_id"], - {"disk_led": False, "heartbeat_led": False, "power_led": False}, - ) + set_yellow_settings.side_effect = exc + result = await hass.config_entries.options.async_configure( + result["flow_id"], + {"disk_led": False, "heartbeat_led": False, "power_led": False}, + ) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "write_hw_settings_error" diff --git a/tests/components/matter/test_init.py b/tests/components/matter/test_init.py index 553358f12e3..496ff12421a 100644 --- a/tests/components/matter/test_init.py +++ b/tests/components/matter/test_init.py @@ -7,6 +7,7 @@ from collections.abc import Generator from unittest.mock import AsyncMock, MagicMock, call, patch from aiohasupervisor import SupervisorError +from aiohasupervisor.models import PartialBackupOptions from matter_server.client.exceptions import ( CannotConnect, NotConnected, @@ -16,7 +17,6 @@ from matter_server.client.exceptions import ( from matter_server.common.errors import MatterError import pytest -from homeassistant.components.hassio import HassioAPIError from homeassistant.components.matter.const import DOMAIN from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState from homeassistant.const import STATE_UNAVAILABLE @@ -399,7 +399,7 @@ async def test_addon_info_failure( 0, 1, None, - HassioAPIError("Boom"), + SupervisorError("Boom"), ServerVersionTooOld("Invalid version"), ), ], @@ -583,9 +583,9 @@ async def test_remove_entry( assert stop_addon.call_args == call("core_matter_server") assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, - {"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]}, - partial=True, + PartialBackupOptions( + name="addon_core_matter_server_1.0.0", addons={"core_matter_server"} + ), ) assert uninstall_addon.call_count == 1 assert uninstall_addon.call_args == call("core_matter_server") @@ -617,7 +617,7 @@ async def test_remove_entry( # test create backup failure entry.add_to_hass(hass) assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - create_backup.side_effect = HassioAPIError() + create_backup.side_effect = SupervisorError() await hass.config_entries.async_remove(entry.entry_id) @@ -625,9 +625,9 @@ async def test_remove_entry( assert stop_addon.call_args == call("core_matter_server") assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, - {"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]}, - partial=True, + PartialBackupOptions( + name="addon_core_matter_server_1.0.0", addons={"core_matter_server"} + ), ) assert uninstall_addon.call_count == 0 assert entry.state is ConfigEntryState.NOT_LOADED @@ -649,9 +649,9 @@ async def test_remove_entry( assert stop_addon.call_args == call("core_matter_server") assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, - {"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]}, - partial=True, + PartialBackupOptions( + name="addon_core_matter_server_1.0.0", addons={"core_matter_server"} + ), ) assert uninstall_addon.call_count == 1 assert uninstall_addon.call_args == call("core_matter_server") diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 1aaa9013d87..a5c3eef6f28 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -8,7 +8,7 @@ from typing import Any from unittest.mock import AsyncMock, MagicMock, call, patch from aiohasupervisor import SupervisorError -from aiohasupervisor.models import AddonsOptions +from aiohasupervisor.models import AddonsOptions, PartialBackupOptions import pytest from zwave_js_server.client import Client from zwave_js_server.const import SecurityClass @@ -22,7 +22,6 @@ from zwave_js_server.model.controller import ProvisioningEntry from zwave_js_server.model.node import Node, NodeDataType from zwave_js_server.model.version import VersionInfo -from homeassistant.components.hassio import HassioAPIError from homeassistant.components.persistent_notification import async_dismiss from homeassistant.components.zwave_js import DOMAIN from homeassistant.components.zwave_js.helpers import get_device_id, get_device_id_ext @@ -1118,7 +1117,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, SupervisorError("Boom"), None), - ("1.0.0", True, 0, 1, None, HassioAPIError("Boom")), + ("1.0.0", True, 0, 1, None, SupervisorError("Boom")), ], ) async def test_update_addon( @@ -1298,9 +1297,7 @@ async def test_remove_entry( assert stop_addon.call_args == call("core_zwave_js") assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, - {"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]}, - partial=True, + PartialBackupOptions(name="addon_core_zwave_js_1.0.0", addons={"core_zwave_js"}) ) assert uninstall_addon.call_count == 1 assert uninstall_addon.call_args == call("core_zwave_js") @@ -1332,7 +1329,7 @@ async def test_remove_entry( # test create backup failure entry.add_to_hass(hass) assert len(hass.config_entries.async_entries(DOMAIN)) == 1 - create_backup.side_effect = HassioAPIError() + create_backup.side_effect = SupervisorError() await hass.config_entries.async_remove(entry.entry_id) @@ -1340,9 +1337,7 @@ async def test_remove_entry( assert stop_addon.call_args == call("core_zwave_js") assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, - {"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]}, - partial=True, + PartialBackupOptions(name="addon_core_zwave_js_1.0.0", addons={"core_zwave_js"}) ) assert uninstall_addon.call_count == 0 assert entry.state is ConfigEntryState.NOT_LOADED @@ -1364,9 +1359,7 @@ async def test_remove_entry( assert stop_addon.call_args == call("core_zwave_js") assert create_backup.call_count == 1 assert create_backup.call_args == call( - hass, - {"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]}, - partial=True, + PartialBackupOptions(name="addon_core_zwave_js_1.0.0", addons={"core_zwave_js"}) ) assert uninstall_addon.call_count == 1 assert uninstall_addon.call_args == call("core_zwave_js")