mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 04:05:20 +01:00
Refactor diagnostics, create backup and green/yellow settings from handler (#154098)
Co-authored-by: Stefan Agner <stefan@agner.ch>
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"})
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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,
|
||||
):
|
||||
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,10 +218,7 @@ 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,
|
||||
):
|
||||
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},
|
||||
|
||||
@@ -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,10 +257,7 @@ 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,
|
||||
):
|
||||
os_yellow_info.side_effect = exc
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
{"next_step_id": "hardware_settings"},
|
||||
@@ -277,8 +266,10 @@ async def test_option_flow_led_settings_fail_1(hass: HomeAssistant) -> None:
|
||||
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,10 +296,7 @@ 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,
|
||||
):
|
||||
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},
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user