Create repairs for unsupported and unhealthy (#80747)

pull/77487/head
Mike Degatano 2022-10-31 09:57:54 -04:00 committed by GitHub
parent 1589c06203
commit 82151bfd40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 690 additions and 21 deletions

View File

@ -77,6 +77,7 @@ from .discovery import HassioServiceInfo, async_setup_discovery_view # noqa: F4
from .handler import HassIO, HassioAPIError, api_data
from .http import HassIOView
from .ingress import async_setup_ingress_view
from .repairs import SupervisorRepairs
from .websocket_api import async_load_websocket_api
_LOGGER = logging.getLogger(__name__)
@ -103,6 +104,7 @@ DATA_SUPERVISOR_INFO = "hassio_supervisor_info"
DATA_ADDONS_CHANGELOGS = "hassio_addons_changelogs"
DATA_ADDONS_INFO = "hassio_addons_info"
DATA_ADDONS_STATS = "hassio_addons_stats"
DATA_SUPERVISOR_REPAIRS = "supervisor_repairs"
HASSIO_UPDATE_INTERVAL = timedelta(minutes=5)
ADDONS_COORDINATOR = "hassio_addons_coordinator"
@ -758,6 +760,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"})
)
# Start listening for problems with supervisor and making repairs
hass.data[DATA_SUPERVISOR_REPAIRS] = repairs = SupervisorRepairs(hass, hassio)
await repairs.setup()
return True

View File

@ -11,19 +11,26 @@ ATTR_CONFIG = "config"
ATTR_DATA = "data"
ATTR_DISCOVERY = "discovery"
ATTR_ENABLE = "enable"
ATTR_ENDPOINT = "endpoint"
ATTR_FOLDERS = "folders"
ATTR_HEALTHY = "healthy"
ATTR_HOMEASSISTANT = "homeassistant"
ATTR_INPUT = "input"
ATTR_METHOD = "method"
ATTR_PANELS = "panels"
ATTR_PASSWORD = "password"
ATTR_RESULT = "result"
ATTR_SUPPORTED = "supported"
ATTR_TIMEOUT = "timeout"
ATTR_TITLE = "title"
ATTR_UNHEALTHY = "unhealthy"
ATTR_UNHEALTHY_REASONS = "unhealthy_reasons"
ATTR_UNSUPPORTED = "unsupported"
ATTR_UNSUPPORTED_REASONS = "unsupported_reasons"
ATTR_UPDATE_KEY = "update_key"
ATTR_USERNAME = "username"
ATTR_UUID = "uuid"
ATTR_WS_EVENT = "event"
ATTR_ENDPOINT = "endpoint"
ATTR_METHOD = "method"
ATTR_RESULT = "result"
ATTR_TIMEOUT = "timeout"
X_AUTH_TOKEN = "X-Supervisor-Token"
X_INGRESS_PATH = "X-Ingress-Path"
@ -38,6 +45,11 @@ WS_TYPE_EVENT = "supervisor/event"
WS_TYPE_SUBSCRIBE = "supervisor/subscribe"
EVENT_SUPERVISOR_EVENT = "supervisor_event"
EVENT_SUPERVISOR_UPDATE = "supervisor_update"
EVENT_HEALTH_CHANGED = "health_changed"
EVENT_SUPPORTED_CHANGED = "supported_changed"
UPDATE_KEY_SUPERVISOR = "supervisor"
ATTR_AUTO_UPDATE = "auto_update"
ATTR_VERSION = "version"
@ -51,7 +63,6 @@ ATTR_STARTED = "started"
ATTR_URL = "url"
ATTR_REPOSITORY = "repository"
DATA_KEY_ADDONS = "addons"
DATA_KEY_OS = "os"
DATA_KEY_SUPERVISOR = "supervisor"

View File

@ -190,6 +190,14 @@ class HassIO:
"""
return self.send_command(f"/discovery/{uuid}", method="get")
@api_data
def get_resolution_info(self):
"""Return data for Supervisor resolution center.
This method return a coroutine.
"""
return self.send_command("/resolution/info", method="get")
@_api_bool
async def update_hass_api(self, http_config, refresh_token):
"""Update Home Assistant API data on Hass.io."""

View File

@ -0,0 +1,138 @@
"""Supervisor events monitor."""
from __future__ import annotations
from typing import Any
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_create_issue,
async_delete_issue,
)
from .const import (
ATTR_DATA,
ATTR_HEALTHY,
ATTR_SUPPORTED,
ATTR_UNHEALTHY,
ATTR_UNHEALTHY_REASONS,
ATTR_UNSUPPORTED,
ATTR_UNSUPPORTED_REASONS,
ATTR_UPDATE_KEY,
ATTR_WS_EVENT,
DOMAIN,
EVENT_HEALTH_CHANGED,
EVENT_SUPERVISOR_EVENT,
EVENT_SUPERVISOR_UPDATE,
EVENT_SUPPORTED_CHANGED,
UPDATE_KEY_SUPERVISOR,
)
from .handler import HassIO
ISSUE_ID_UNHEALTHY = "unhealthy_system"
ISSUE_ID_UNSUPPORTED = "unsupported_system"
INFO_URL_UNHEALTHY = "https://www.home-assistant.io/more-info/unhealthy"
INFO_URL_UNSUPPORTED = "https://www.home-assistant.io/more-info/unsupported"
class SupervisorRepairs:
"""Create repairs from supervisor events."""
def __init__(self, hass: HomeAssistant, client: HassIO) -> None:
"""Initialize supervisor repairs."""
self._hass = hass
self._client = client
self._unsupported_reasons: set[str] = set()
self._unhealthy_reasons: set[str] = set()
@property
def unhealthy_reasons(self) -> set[str]:
"""Get unhealthy reasons. Returns empty set if system is healthy."""
return self._unhealthy_reasons
@unhealthy_reasons.setter
def unhealthy_reasons(self, reasons: set[str]) -> None:
"""Set unhealthy reasons. Create or delete repairs as necessary."""
for unhealthy in reasons - self.unhealthy_reasons:
async_create_issue(
self._hass,
DOMAIN,
f"{ISSUE_ID_UNHEALTHY}_{unhealthy}",
is_fixable=False,
learn_more_url=f"{INFO_URL_UNHEALTHY}/{unhealthy}",
severity=IssueSeverity.CRITICAL,
translation_key="unhealthy",
translation_placeholders={"reason": unhealthy},
)
for fixed in self.unhealthy_reasons - reasons:
async_delete_issue(self._hass, DOMAIN, f"{ISSUE_ID_UNHEALTHY}_{fixed}")
self._unhealthy_reasons = reasons
@property
def unsupported_reasons(self) -> set[str]:
"""Get unsupported reasons. Returns empty set if system is supported."""
return self._unsupported_reasons
@unsupported_reasons.setter
def unsupported_reasons(self, reasons: set[str]) -> None:
"""Set unsupported reasons. Create or delete repairs as necessary."""
for unsupported in reasons - self.unsupported_reasons:
async_create_issue(
self._hass,
DOMAIN,
f"{ISSUE_ID_UNSUPPORTED}_{unsupported}",
is_fixable=False,
learn_more_url=f"{INFO_URL_UNSUPPORTED}/{unsupported}",
severity=IssueSeverity.WARNING,
translation_key="unsupported",
translation_placeholders={"reason": unsupported},
)
for fixed in self.unsupported_reasons - reasons:
async_delete_issue(self._hass, DOMAIN, f"{ISSUE_ID_UNSUPPORTED}_{fixed}")
self._unsupported_reasons = reasons
async def setup(self) -> None:
"""Create supervisor events listener."""
await self.update()
async_dispatcher_connect(
self._hass, EVENT_SUPERVISOR_EVENT, self._supervisor_events_to_repairs
)
async def update(self) -> None:
"""Update repairs from Supervisor resolution center."""
data = await self._client.get_resolution_info()
self.unhealthy_reasons = set(data[ATTR_UNHEALTHY])
self.unsupported_reasons = set(data[ATTR_UNSUPPORTED])
@callback
def _supervisor_events_to_repairs(self, event: dict[str, Any]) -> None:
"""Create repairs from supervisor events."""
if ATTR_WS_EVENT not in event:
return
if (
event[ATTR_WS_EVENT] == EVENT_SUPERVISOR_UPDATE
and event.get(ATTR_UPDATE_KEY) == UPDATE_KEY_SUPERVISOR
):
self._hass.async_create_task(self.update())
elif event[ATTR_WS_EVENT] == EVENT_HEALTH_CHANGED:
self.unhealthy_reasons = (
set()
if event[ATTR_DATA][ATTR_HEALTHY]
else set(event[ATTR_DATA][ATTR_UNHEALTHY_REASONS])
)
elif event[ATTR_WS_EVENT] == EVENT_SUPPORTED_CHANGED:
self.unsupported_reasons = (
set()
if event[ATTR_DATA][ATTR_SUPPORTED]
else set(event[ATTR_DATA][ATTR_UNSUPPORTED_REASONS])
)

View File

@ -15,5 +15,15 @@
"update_channel": "Update Channel",
"version_api": "Version API"
}
},
"issues": {
"unhealthy": {
"title": "Unhealthy system - {reason}",
"description": "System is currently unhealthy due to '{reason}'. Use the link to learn more about what is wrong and how to fix it."
},
"unsupported": {
"title": "Unsupported system - {reason}",
"description": "System is unsupported due to '{reason}'. Use the link to learn more about what this means and how to return to a supported system."
}
}
}

View File

@ -133,6 +133,19 @@ def mock_all(aioclient_mock, request):
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={
"result": "ok",
"data": {
"unsupported": [],
"unhealthy": [],
"suggestions": [],
"issues": [],
"checks": [],
},
},
)
@pytest.mark.parametrize(

View File

@ -139,6 +139,19 @@ def mock_all(aioclient_mock, request):
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={
"result": "ok",
"data": {
"unsupported": [],
"unhealthy": [],
"suggestions": [],
"issues": [],
"checks": [],
},
},
)
async def test_diagnostics(

View File

@ -183,6 +183,19 @@ def mock_all(aioclient_mock, request, os_info):
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={
"result": "ok",
"data": {
"unsupported": [],
"unhealthy": [],
"suggestions": [],
"issues": [],
"checks": [],
},
},
)
async def test_setup_api_ping(hass, aioclient_mock):
@ -191,7 +204,7 @@ async def test_setup_api_ping(hass, aioclient_mock):
result = await async_setup_component(hass, "hassio", {})
assert result
assert aioclient_mock.call_count == 15
assert aioclient_mock.call_count == 16
assert hass.components.hassio.get_core_info()["version_latest"] == "1.0.0"
assert hass.components.hassio.is_hassio()
@ -230,7 +243,7 @@ async def test_setup_api_push_api_data(hass, aioclient_mock):
)
assert result
assert aioclient_mock.call_count == 15
assert aioclient_mock.call_count == 16
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 9999
assert aioclient_mock.mock_calls[1][2]["watchdog"]
@ -246,7 +259,7 @@ async def test_setup_api_push_api_data_server_host(hass, aioclient_mock):
)
assert result
assert aioclient_mock.call_count == 15
assert aioclient_mock.call_count == 16
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 9999
assert not aioclient_mock.mock_calls[1][2]["watchdog"]
@ -258,7 +271,7 @@ async def test_setup_api_push_api_data_default(hass, aioclient_mock, hass_storag
result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}})
assert result
assert aioclient_mock.call_count == 15
assert aioclient_mock.call_count == 16
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 8123
refresh_token = aioclient_mock.mock_calls[1][2]["refresh_token"]
@ -325,7 +338,7 @@ async def test_setup_api_existing_hassio_user(hass, aioclient_mock, hass_storage
result = await async_setup_component(hass, "hassio", {"http": {}, "hassio": {}})
assert result
assert aioclient_mock.call_count == 15
assert aioclient_mock.call_count == 16
assert not aioclient_mock.mock_calls[1][2]["ssl"]
assert aioclient_mock.mock_calls[1][2]["port"] == 8123
assert aioclient_mock.mock_calls[1][2]["refresh_token"] == token.token
@ -339,7 +352,7 @@ async def test_setup_core_push_timezone(hass, aioclient_mock):
result = await async_setup_component(hass, "hassio", {"hassio": {}})
assert result
assert aioclient_mock.call_count == 15
assert aioclient_mock.call_count == 16
assert aioclient_mock.mock_calls[2][2]["timezone"] == "testzone"
with patch("homeassistant.util.dt.set_default_time_zone"):
@ -356,7 +369,7 @@ async def test_setup_hassio_no_additional_data(hass, aioclient_mock):
result = await async_setup_component(hass, "hassio", {"hassio": {}})
assert result
assert aioclient_mock.call_count == 15
assert aioclient_mock.call_count == 16
assert aioclient_mock.mock_calls[-1][3]["Authorization"] == "Bearer 123456"
@ -426,14 +439,14 @@ async def test_service_calls(hassio_env, hass, aioclient_mock, caplog):
)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 9
assert aioclient_mock.call_count == 10
assert aioclient_mock.mock_calls[-1][2] == "test"
await hass.services.async_call("hassio", "host_shutdown", {})
await hass.services.async_call("hassio", "host_reboot", {})
await hass.async_block_till_done()
assert aioclient_mock.call_count == 11
assert aioclient_mock.call_count == 12
await hass.services.async_call("hassio", "backup_full", {})
await hass.services.async_call(
@ -448,7 +461,7 @@ async def test_service_calls(hassio_env, hass, aioclient_mock, caplog):
)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 13
assert aioclient_mock.call_count == 14
assert aioclient_mock.mock_calls[-1][2] == {
"homeassistant": True,
"addons": ["test"],
@ -472,7 +485,7 @@ async def test_service_calls(hassio_env, hass, aioclient_mock, caplog):
)
await hass.async_block_till_done()
assert aioclient_mock.call_count == 15
assert aioclient_mock.call_count == 16
assert aioclient_mock.mock_calls[-1][2] == {
"addons": ["test"],
"folders": ["ssl"],
@ -491,12 +504,12 @@ async def test_service_calls_core(hassio_env, hass, aioclient_mock):
await hass.services.async_call("homeassistant", "stop")
await hass.async_block_till_done()
assert aioclient_mock.call_count == 5
assert aioclient_mock.call_count == 6
await hass.services.async_call("homeassistant", "check_config")
await hass.async_block_till_done()
assert aioclient_mock.call_count == 5
assert aioclient_mock.call_count == 6
with patch(
"homeassistant.config.async_check_ha_config_file", return_value=None
@ -505,7 +518,7 @@ async def test_service_calls_core(hassio_env, hass, aioclient_mock):
await hass.async_block_till_done()
assert mock_check_config.called
assert aioclient_mock.call_count == 6
assert aioclient_mock.call_count == 7
async def test_entry_load_and_unload(hass):
@ -758,7 +771,7 @@ async def test_setup_hardware_integration(hass, aioclient_mock, integration):
assert result
await hass.async_block_till_done()
assert aioclient_mock.call_count == 15
assert aioclient_mock.call_count == 16
assert len(mock_setup_entry.mock_calls) == 1

View File

@ -0,0 +1,395 @@
"""Test repairs from supervisor issues."""
from __future__ import annotations
import os
from typing import Any
from unittest.mock import ANY, patch
import pytest
from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from .test_init import MOCK_ENVIRON
from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture(autouse=True)
async def setup_repairs(hass):
"""Set up the repairs integration."""
assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})
@pytest.fixture(autouse=True)
def mock_all(aioclient_mock: AiohttpClientMocker, request: pytest.FixtureRequest):
"""Mock all setup requests."""
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"})
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/info",
json={
"result": "ok",
"data": {
"supervisor": "222",
"homeassistant": "0.110.0",
"hassos": "1.2.3",
},
},
)
aioclient_mock.get(
"http://127.0.0.1/store",
json={
"result": "ok",
"data": {"addons": [], "repositories": []},
},
)
aioclient_mock.get(
"http://127.0.0.1/host/info",
json={
"result": "ok",
"data": {
"result": "ok",
"data": {
"chassis": "vm",
"operating_system": "Debian GNU/Linux 10 (buster)",
"kernel": "4.19.0-6-amd64",
},
},
},
)
aioclient_mock.get(
"http://127.0.0.1/core/info",
json={"result": "ok", "data": {"version_latest": "1.0.0", "version": "1.0.0"}},
)
aioclient_mock.get(
"http://127.0.0.1/os/info",
json={
"result": "ok",
"data": {
"version_latest": "1.0.0",
"version": "1.0.0",
"update_available": False,
},
},
)
aioclient_mock.get(
"http://127.0.0.1/supervisor/info",
json={
"result": "ok",
"data": {
"result": "ok",
"version": "1.0.0",
"version_latest": "1.0.0",
"auto_update": True,
"addons": [],
},
},
)
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
@pytest.fixture(autouse=True)
async def fixture_supervisor_environ():
"""Mock os environ for supervisor."""
with patch.dict(os.environ, MOCK_ENVIRON):
yield
def mock_resolution_info(
aioclient_mock: AiohttpClientMocker,
unsupported: list[str] | None = None,
unhealthy: list[str] | None = None,
):
"""Mock resolution/info endpoint with unsupported/unhealthy reasons."""
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={
"result": "ok",
"data": {
"unsupported": unsupported or [],
"unhealthy": unhealthy or [],
"suggestions": [],
"issues": [],
"checks": [
{"enabled": True, "slug": "supervisor_trust"},
{"enabled": True, "slug": "free_space"},
],
},
},
)
def assert_repair_in_list(issues: list[dict[str, Any]], unhealthy: bool, reason: str):
"""Assert repair for unhealthy/unsupported in list."""
repair_type = "unhealthy" if unhealthy else "unsupported"
assert {
"breaks_in_ha_version": None,
"created": ANY,
"dismissed_version": None,
"domain": "hassio",
"ignored": False,
"is_fixable": False,
"issue_id": f"{repair_type}_system_{reason}",
"issue_domain": None,
"learn_more_url": f"https://www.home-assistant.io/more-info/{repair_type}/{reason}",
"severity": "critical" if unhealthy else "warning",
"translation_key": repair_type,
"translation_placeholders": {
"reason": reason,
},
} in issues
async def test_unhealthy_repairs(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
hass_ws_client,
):
"""Test repairs added for unhealthy systems."""
mock_resolution_info(aioclient_mock, unhealthy=["docker", "setup"])
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="setup")
async def test_unsupported_repairs(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
hass_ws_client,
):
"""Test repairs added for unsupported systems."""
mock_resolution_info(aioclient_mock, unsupported=["content_trust", "os"])
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert_repair_in_list(
msg["result"]["issues"], unhealthy=False, reason="content_trust"
)
assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")
async def test_unhealthy_repairs_add_remove(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
hass_ws_client,
):
"""Test unhealthy repairs added and removed from dispatches."""
mock_resolution_info(aioclient_mock)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "health_changed",
"data": {
"healthy": False,
"unhealthy_reasons": ["docker"],
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
await client.send_json(
{
"id": 3,
"type": "supervisor/event",
"data": {
"event": "health_changed",
"data": {"healthy": True},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 4, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {"issues": []}
async def test_unsupported_repairs_add_remove(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
hass_ws_client,
):
"""Test unsupported repairs added and removed from dispatches."""
mock_resolution_info(aioclient_mock)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "supported_changed",
"data": {
"supported": False,
"unsupported_reasons": ["os"],
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")
await client.send_json(
{
"id": 3,
"type": "supervisor/event",
"data": {
"event": "supported_changed",
"data": {"supported": True},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 4, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {"issues": []}
async def test_reset_repairs_supervisor_restart(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
hass_ws_client,
):
"""Unsupported/unhealthy repairs reset on supervisor restart."""
mock_resolution_info(aioclient_mock, unsupported=["os"], unhealthy=["docker"])
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")
aioclient_mock.clear_requests()
mock_resolution_info(aioclient_mock)
await client.send_json(
{
"id": 2,
"type": "supervisor/event",
"data": {
"event": "supervisor_update",
"update_key": "supervisor",
"data": {},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 3, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {"issues": []}
async def test_reasons_added_and_removed(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
hass_ws_client,
):
"""Test an unsupported/unhealthy reasons being added and removed at same time."""
mock_resolution_info(aioclient_mock, unsupported=["os"], unhealthy=["docker"])
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")
aioclient_mock.clear_requests()
mock_resolution_info(
aioclient_mock, unsupported=["content_trust"], unhealthy=["setup"]
)
await client.send_json(
{
"id": 2,
"type": "supervisor/event",
"data": {
"event": "supervisor_update",
"update_key": "supervisor",
"data": {},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 3, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="setup")
assert_repair_in_list(
msg["result"]["issues"], unhealthy=False, reason="content_trust"
)

View File

@ -126,6 +126,19 @@ def mock_all(aioclient_mock, request):
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={
"result": "ok",
"data": {
"unsupported": [],
"unhealthy": [],
"suggestions": [],
"issues": [],
"checks": [],
},
},
)
@pytest.mark.parametrize(

View File

@ -139,6 +139,19 @@ def mock_all(aioclient_mock, request):
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={
"result": "ok",
"data": {
"unsupported": [],
"unhealthy": [],
"suggestions": [],
"issues": [],
"checks": [],
},
},
)
@pytest.mark.parametrize(

View File

@ -61,6 +61,19 @@ def mock_all(aioclient_mock):
aioclient_mock.get(
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
)
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={
"result": "ok",
"data": {
"unsupported": [],
"unhealthy": [],
"suggestions": [],
"issues": [],
"checks": [],
},
},
)
async def test_ws_subscription(hassio_env, hass: HomeAssistant, hass_ws_client):

View File

@ -198,7 +198,17 @@ async def test_access_from_supervisor_ip(
manager: IpBanManager = app[KEY_BAN_MANAGER]
assert await async_setup_component(hass, "hassio", {"hassio": {}})
with patch(
"homeassistant.components.hassio.HassIO.get_resolution_info",
return_value={
"unsupported": [],
"unhealthy": [],
"suggestions": [],
"issues": [],
"checks": [],
},
):
assert await async_setup_component(hass, "hassio", {"hassio": {}})
m_open = mock_open()

View File

@ -57,6 +57,19 @@ async def mock_supervisor_fixture(hass, aioclient_mock):
"""Mock supervisor."""
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
aioclient_mock.get(
"http://127.0.0.1/resolution/info",
json={
"result": "ok",
"data": {
"unsupported": [],
"unhealthy": [],
"suggestions": [],
"issues": [],
"checks": [],
},
},
)
with patch.dict(os.environ, {"SUPERVISOR": "127.0.0.1"}), patch(
"homeassistant.components.hassio.HassIO.is_connected",
return_value=True,