Refactor diagnostics, create backup and green/yellow settings from handler (#154098)

Co-authored-by: Stefan Agner <stefan@agner.ch>
This commit is contained in:
Mike Degatano
2025-10-28 16:46:06 -04:00
committed by GitHub
parent 58182a344d
commit d6ae0c142e
13 changed files with 154 additions and 321 deletions

View File

@@ -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,
)

View File

@@ -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(

View File

@@ -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)
)

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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."""

View File

@@ -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(

View File

@@ -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"})
)

View File

@@ -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."""

View File

@@ -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"

View File

@@ -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"

View File

@@ -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")

View File

@@ -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")