mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Replace Supervisor resolution API calls with aiohasupervisor (#129599)
* Replace Supervisor resolution API calls with aiohasupervisor * Use consistent types to avoid uuid issues * Fix mocking in http test * Changes from feedback * Put hass first * Fix typo --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
03d5b18974
commit
ed4f55406c
@ -137,17 +137,3 @@ class SupervisorEntityModel(StrEnum):
|
||||
CORE = "Home Assistant Core"
|
||||
SUPERVIOSR = "Home Assistant Supervisor"
|
||||
HOST = "Home Assistant Host"
|
||||
|
||||
|
||||
class SupervisorIssueContext(StrEnum):
|
||||
"""Context for supervisor issues."""
|
||||
|
||||
ADDON = "addon"
|
||||
CORE = "core"
|
||||
DNS_SERVER = "dns_server"
|
||||
MOUNT = "mount"
|
||||
OS = "os"
|
||||
PLUGIN = "plugin"
|
||||
SUPERVISOR = "supervisor"
|
||||
STORE = "store"
|
||||
SYSTEM = "system"
|
||||
|
@ -91,15 +91,6 @@ async def async_create_backup(
|
||||
return await hassio.send_command(command, payload=payload, timeout=None)
|
||||
|
||||
|
||||
@bind_hass
|
||||
@_api_bool
|
||||
async def async_apply_suggestion(hass: HomeAssistant, suggestion_uuid: str) -> dict:
|
||||
"""Apply a suggestion from supervisor's resolution center."""
|
||||
hassio: HassIO = hass.data[DOMAIN]
|
||||
command = f"/resolution/suggestion/{suggestion_uuid}"
|
||||
return await hassio.send_command(command, timeout=None)
|
||||
|
||||
|
||||
@api_data
|
||||
async def async_get_green_settings(hass: HomeAssistant) -> dict[str, bool]:
|
||||
"""Return settings specific to Home Assistant Green."""
|
||||
@ -245,26 +236,6 @@ class HassIO:
|
||||
"""
|
||||
return self.send_command("/ingress/panels", method="get")
|
||||
|
||||
@api_data
|
||||
def get_resolution_info(self) -> Coroutine:
|
||||
"""Return data for Supervisor resolution center.
|
||||
|
||||
This method returns a coroutine.
|
||||
"""
|
||||
return self.send_command("/resolution/info", method="get")
|
||||
|
||||
@api_data
|
||||
def get_suggestions_for_issue(
|
||||
self, issue_id: str
|
||||
) -> Coroutine[Any, Any, dict[str, Any]]:
|
||||
"""Return suggestions for issue from Supervisor resolution center.
|
||||
|
||||
This method returns a coroutine.
|
||||
"""
|
||||
return self.send_command(
|
||||
f"/resolution/issue/{issue_id}/suggestions", method="get"
|
||||
)
|
||||
|
||||
@_api_bool
|
||||
async def update_hass_api(
|
||||
self, http_config: dict[str, Any], refresh_token: RefreshToken
|
||||
@ -304,14 +275,6 @@ class HassIO:
|
||||
"/supervisor/options", payload={"diagnostics": diagnostics}
|
||||
)
|
||||
|
||||
@_api_bool
|
||||
def apply_suggestion(self, suggestion_uuid: str) -> Coroutine:
|
||||
"""Apply a suggestion from supervisor's resolution center.
|
||||
|
||||
This method returns a coroutine.
|
||||
"""
|
||||
return self.send_command(f"/resolution/suggestion/{suggestion_uuid}")
|
||||
|
||||
async def send_command(
|
||||
self,
|
||||
command: str,
|
||||
|
@ -7,6 +7,10 @@ from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from typing import Any, NotRequired, TypedDict
|
||||
from uuid import UUID
|
||||
|
||||
from aiohasupervisor import SupervisorError
|
||||
from aiohasupervisor.models import ContextType, Issue as SupervisorIssue
|
||||
|
||||
from homeassistant.core import HassJob, HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
@ -20,12 +24,8 @@ from homeassistant.helpers.issue_registry import (
|
||||
from .const import (
|
||||
ATTR_DATA,
|
||||
ATTR_HEALTHY,
|
||||
ATTR_ISSUES,
|
||||
ATTR_SUGGESTIONS,
|
||||
ATTR_SUPPORTED,
|
||||
ATTR_UNHEALTHY,
|
||||
ATTR_UNHEALTHY_REASONS,
|
||||
ATTR_UNSUPPORTED,
|
||||
ATTR_UNSUPPORTED_REASONS,
|
||||
ATTR_UPDATE_KEY,
|
||||
ATTR_WS_EVENT,
|
||||
@ -45,10 +45,9 @@ from .const import (
|
||||
PLACEHOLDER_KEY_REFERENCE,
|
||||
REQUEST_REFRESH_DELAY,
|
||||
UPDATE_KEY_SUPERVISOR,
|
||||
SupervisorIssueContext,
|
||||
)
|
||||
from .coordinator import get_addons_info
|
||||
from .handler import HassIO, HassioAPIError
|
||||
from .handler import HassIO, get_supervisor_client
|
||||
|
||||
ISSUE_KEY_UNHEALTHY = "unhealthy"
|
||||
ISSUE_KEY_UNSUPPORTED = "unsupported"
|
||||
@ -120,9 +119,9 @@ class SuggestionDataType(TypedDict):
|
||||
class Suggestion:
|
||||
"""Suggestion from Supervisor which resolves an issue."""
|
||||
|
||||
uuid: str
|
||||
uuid: UUID
|
||||
type: str
|
||||
context: SupervisorIssueContext
|
||||
context: ContextType
|
||||
reference: str | None = None
|
||||
|
||||
@property
|
||||
@ -134,9 +133,9 @@ class Suggestion:
|
||||
def from_dict(cls, data: SuggestionDataType) -> Suggestion:
|
||||
"""Convert from dictionary representation."""
|
||||
return cls(
|
||||
uuid=data["uuid"],
|
||||
uuid=UUID(data["uuid"]),
|
||||
type=data["type"],
|
||||
context=SupervisorIssueContext(data["context"]),
|
||||
context=ContextType(data["context"]),
|
||||
reference=data["reference"],
|
||||
)
|
||||
|
||||
@ -155,9 +154,9 @@ class IssueDataType(TypedDict):
|
||||
class Issue:
|
||||
"""Issue from Supervisor."""
|
||||
|
||||
uuid: str
|
||||
uuid: UUID
|
||||
type: str
|
||||
context: SupervisorIssueContext
|
||||
context: ContextType
|
||||
reference: str | None = None
|
||||
suggestions: list[Suggestion] = field(default_factory=list, compare=False)
|
||||
|
||||
@ -171,9 +170,9 @@ class Issue:
|
||||
"""Convert from dictionary representation."""
|
||||
suggestions: list[SuggestionDataType] = data.get("suggestions", [])
|
||||
return cls(
|
||||
uuid=data["uuid"],
|
||||
uuid=UUID(data["uuid"]),
|
||||
type=data["type"],
|
||||
context=SupervisorIssueContext(data["context"]),
|
||||
context=ContextType(data["context"]),
|
||||
reference=data["reference"],
|
||||
suggestions=[
|
||||
Suggestion.from_dict(suggestion) for suggestion in suggestions
|
||||
@ -190,7 +189,8 @@ class SupervisorIssues:
|
||||
self._client = client
|
||||
self._unsupported_reasons: set[str] = set()
|
||||
self._unhealthy_reasons: set[str] = set()
|
||||
self._issues: dict[str, Issue] = {}
|
||||
self._issues: dict[UUID, Issue] = {}
|
||||
self._supervisor_client = get_supervisor_client(hass)
|
||||
|
||||
@property
|
||||
def unhealthy_reasons(self) -> set[str]:
|
||||
@ -283,7 +283,7 @@ class SupervisorIssues:
|
||||
async_create_issue(
|
||||
self._hass,
|
||||
DOMAIN,
|
||||
issue.uuid,
|
||||
issue.uuid.hex,
|
||||
is_fixable=bool(issue.suggestions),
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key=issue.key,
|
||||
@ -292,19 +292,37 @@ class SupervisorIssues:
|
||||
|
||||
self._issues[issue.uuid] = issue
|
||||
|
||||
async def add_issue_from_data(self, data: IssueDataType) -> None:
|
||||
async def add_issue_from_data(self, data: SupervisorIssue) -> None:
|
||||
"""Add issue from data to list after getting latest suggestions."""
|
||||
try:
|
||||
data["suggestions"] = (
|
||||
await self._client.get_suggestions_for_issue(data["uuid"])
|
||||
)[ATTR_SUGGESTIONS]
|
||||
except HassioAPIError:
|
||||
suggestions = (
|
||||
await self._supervisor_client.resolution.suggestions_for_issue(
|
||||
data.uuid
|
||||
)
|
||||
)
|
||||
except SupervisorError:
|
||||
_LOGGER.error(
|
||||
"Could not get suggestions for supervisor issue %s, skipping it",
|
||||
data["uuid"],
|
||||
data.uuid.hex,
|
||||
)
|
||||
return
|
||||
self.add_issue(Issue.from_dict(data))
|
||||
self.add_issue(
|
||||
Issue(
|
||||
uuid=data.uuid,
|
||||
type=str(data.type),
|
||||
context=data.context,
|
||||
reference=data.reference,
|
||||
suggestions=[
|
||||
Suggestion(
|
||||
uuid=suggestion.uuid,
|
||||
type=str(suggestion.type),
|
||||
context=suggestion.context,
|
||||
reference=suggestion.reference,
|
||||
)
|
||||
for suggestion in suggestions
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
def remove_issue(self, issue: Issue) -> None:
|
||||
"""Remove an issue from the list. Delete a repair if necessary."""
|
||||
@ -312,13 +330,13 @@ class SupervisorIssues:
|
||||
return
|
||||
|
||||
if issue.key in ISSUE_KEYS_FOR_REPAIRS:
|
||||
async_delete_issue(self._hass, DOMAIN, issue.uuid)
|
||||
async_delete_issue(self._hass, DOMAIN, issue.uuid.hex)
|
||||
|
||||
del self._issues[issue.uuid]
|
||||
|
||||
def get_issue(self, issue_id: str) -> Issue | None:
|
||||
"""Get issue from key."""
|
||||
return self._issues.get(issue_id)
|
||||
return self._issues.get(UUID(issue_id))
|
||||
|
||||
async def setup(self) -> None:
|
||||
"""Create supervisor events listener."""
|
||||
@ -331,8 +349,8 @@ class SupervisorIssues:
|
||||
async def _update(self, _: datetime | None = None) -> None:
|
||||
"""Update issues from Supervisor resolution center."""
|
||||
try:
|
||||
data = await self._client.get_resolution_info()
|
||||
except HassioAPIError as err:
|
||||
data = await self._supervisor_client.resolution.info()
|
||||
except SupervisorError as err:
|
||||
_LOGGER.error("Failed to update supervisor issues: %r", err)
|
||||
async_call_later(
|
||||
self._hass,
|
||||
@ -340,18 +358,16 @@ class SupervisorIssues:
|
||||
HassJob(self._update, cancel_on_shutdown=True),
|
||||
)
|
||||
return
|
||||
self.unhealthy_reasons = set(data[ATTR_UNHEALTHY])
|
||||
self.unsupported_reasons = set(data[ATTR_UNSUPPORTED])
|
||||
self.unhealthy_reasons = set(data.unhealthy)
|
||||
self.unsupported_reasons = set(data.unsupported)
|
||||
|
||||
# Remove any cached issues that weren't returned
|
||||
for issue_id in set(self._issues.keys()) - {
|
||||
issue["uuid"] for issue in data[ATTR_ISSUES]
|
||||
}:
|
||||
for issue_id in set(self._issues) - {issue.uuid for issue in data.issues}:
|
||||
self.remove_issue(self._issues[issue_id])
|
||||
|
||||
# Add/update any issues that came back
|
||||
await asyncio.gather(
|
||||
*[self.add_issue_from_data(issue) for issue in data[ATTR_ISSUES]]
|
||||
*[self.add_issue_from_data(issue) for issue in data.issues]
|
||||
)
|
||||
|
||||
@callback
|
||||
|
@ -6,6 +6,8 @@ from collections.abc import Callable, Coroutine
|
||||
from types import MethodType
|
||||
from typing import Any
|
||||
|
||||
from aiohasupervisor import SupervisorError
|
||||
from aiohasupervisor.models import ContextType
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.repairs import RepairsFlow
|
||||
@ -20,9 +22,8 @@ from .const import (
|
||||
PLACEHOLDER_KEY_ADDON,
|
||||
PLACEHOLDER_KEY_COMPONENTS,
|
||||
PLACEHOLDER_KEY_REFERENCE,
|
||||
SupervisorIssueContext,
|
||||
)
|
||||
from .handler import async_apply_suggestion
|
||||
from .handler import get_supervisor_client
|
||||
from .issues import Issue, Suggestion
|
||||
|
||||
HELP_URLS = {
|
||||
@ -51,9 +52,10 @@ class SupervisorIssueRepairFlow(RepairsFlow):
|
||||
_data: dict[str, Any] | None = None
|
||||
_issue: Issue | None = None
|
||||
|
||||
def __init__(self, issue_id: str) -> None:
|
||||
def __init__(self, hass: HomeAssistant, issue_id: str) -> None:
|
||||
"""Initialize repair flow."""
|
||||
self._issue_id = issue_id
|
||||
self._supervisor_client = get_supervisor_client(hass)
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
@ -124,9 +126,12 @@ class SupervisorIssueRepairFlow(RepairsFlow):
|
||||
if not confirmed and suggestion.key in SUGGESTION_CONFIRMATION_REQUIRED:
|
||||
return self._async_form_for_suggestion(suggestion)
|
||||
|
||||
if await async_apply_suggestion(self.hass, suggestion.uuid):
|
||||
return self.async_create_entry(data={})
|
||||
return self.async_abort(reason="apply_suggestion_fail")
|
||||
try:
|
||||
await self._supervisor_client.resolution.apply_suggestion(suggestion.uuid)
|
||||
except SupervisorError:
|
||||
return self.async_abort(reason="apply_suggestion_fail")
|
||||
|
||||
return self.async_create_entry(data={})
|
||||
|
||||
@staticmethod
|
||||
def _async_step(
|
||||
@ -163,9 +168,9 @@ class DockerConfigIssueRepairFlow(SupervisorIssueRepairFlow):
|
||||
if issue.key == self.issue.key or issue.type != self.issue.type:
|
||||
continue
|
||||
|
||||
if issue.context == SupervisorIssueContext.CORE:
|
||||
if issue.context == ContextType.CORE:
|
||||
components.insert(0, "Home Assistant")
|
||||
elif issue.context == SupervisorIssueContext.ADDON:
|
||||
elif issue.context == ContextType.ADDON:
|
||||
components.append(
|
||||
next(
|
||||
(
|
||||
@ -210,11 +215,11 @@ async def async_create_fix_flow(
|
||||
supervisor_issues = get_issues_info(hass)
|
||||
issue = supervisor_issues and supervisor_issues.get_issue(issue_id)
|
||||
if issue and issue.key == ISSUE_KEY_SYSTEM_DOCKER_CONFIG:
|
||||
return DockerConfigIssueRepairFlow(issue_id)
|
||||
return DockerConfigIssueRepairFlow(hass, issue_id)
|
||||
if issue and issue.key in {
|
||||
ISSUE_KEY_ADDON_DETACHED_ADDON_REMOVED,
|
||||
ISSUE_KEY_ADDON_BOOT_FAIL,
|
||||
}:
|
||||
return AddonIssueRepairFlow(issue_id)
|
||||
return AddonIssueRepairFlow(hass, issue_id)
|
||||
|
||||
return SupervisorIssueRepairFlow(issue_id)
|
||||
return SupervisorIssueRepairFlow(hass, issue_id)
|
||||
|
@ -8,7 +8,13 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from aiohasupervisor.models import Discovery, Repository, StoreAddon, StoreInfo
|
||||
from aiohasupervisor.models import (
|
||||
Discovery,
|
||||
Repository,
|
||||
ResolutionInfo,
|
||||
StoreAddon,
|
||||
StoreInfo,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.config_entries import (
|
||||
@ -473,6 +479,26 @@ def supervisor_is_connected_fixture(supervisor_client: AsyncMock) -> AsyncMock:
|
||||
return supervisor_client.supervisor.ping
|
||||
|
||||
|
||||
@pytest.fixture(name="resolution_info")
|
||||
def resolution_info_fixture(supervisor_client: AsyncMock) -> AsyncMock:
|
||||
"""Mock resolution info from supervisor."""
|
||||
supervisor_client.resolution.info.return_value = ResolutionInfo(
|
||||
suggestions=[],
|
||||
unsupported=[],
|
||||
unhealthy=[],
|
||||
issues=[],
|
||||
checks=[],
|
||||
)
|
||||
return supervisor_client.resolution.info
|
||||
|
||||
|
||||
@pytest.fixture(name="resolution_suggestions_for_issue")
|
||||
def resolution_suggestions_for_issue_fixture(supervisor_client: AsyncMock) -> AsyncMock:
|
||||
"""Mock suggestions by issue from supervisor resolution."""
|
||||
supervisor_client.resolution.suggestions_for_issue.return_value = []
|
||||
return supervisor_client.resolution.suggestions_for_issue
|
||||
|
||||
|
||||
@pytest.fixture(name="supervisor_client")
|
||||
def supervisor_client() -> Generator[AsyncMock]:
|
||||
"""Mock the supervisor client."""
|
||||
@ -481,6 +507,7 @@ def supervisor_client() -> Generator[AsyncMock]:
|
||||
supervisor_client.discovery = AsyncMock()
|
||||
supervisor_client.homeassistant = AsyncMock()
|
||||
supervisor_client.os = AsyncMock()
|
||||
supervisor_client.resolution = AsyncMock()
|
||||
supervisor_client.supervisor = AsyncMock()
|
||||
with (
|
||||
patch(
|
||||
@ -504,7 +531,11 @@ def supervisor_client() -> Generator[AsyncMock]:
|
||||
return_value=supervisor_client,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hassio.get_supervisor_client",
|
||||
"homeassistant.components.hassio.issues.get_supervisor_client",
|
||||
return_value=supervisor_client,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hassio.repairs.get_supervisor_client",
|
||||
return_value=supervisor_client,
|
||||
),
|
||||
):
|
||||
|
@ -25,6 +25,7 @@ def mock_all(
|
||||
store_info: AsyncMock,
|
||||
addon_changelog: AsyncMock,
|
||||
addon_stats: AsyncMock,
|
||||
resolution_info: AsyncMock,
|
||||
) -> None:
|
||||
"""Mock all setup requests."""
|
||||
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
|
||||
@ -140,19 +141,6 @@ def mock_all(
|
||||
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": [],
|
||||
},
|
||||
},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/network/info",
|
||||
json={
|
||||
|
@ -24,6 +24,7 @@ def mock_all(
|
||||
store_info: AsyncMock,
|
||||
addon_stats: AsyncMock,
|
||||
addon_changelog: AsyncMock,
|
||||
resolution_info: AsyncMock,
|
||||
) -> None:
|
||||
"""Mock all setup requests."""
|
||||
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
|
||||
@ -143,19 +144,6 @@ def mock_all(
|
||||
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": [],
|
||||
},
|
||||
},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/network/info",
|
||||
json={
|
||||
|
@ -208,7 +208,7 @@ async def test_api_ingress_panels(
|
||||
@pytest.mark.parametrize(
|
||||
("api_call", "method", "payload"),
|
||||
[
|
||||
("get_resolution_info", "GET", None),
|
||||
("get_network_info", "GET", None),
|
||||
("update_diagnostics", "POST", True),
|
||||
],
|
||||
)
|
||||
|
@ -67,6 +67,7 @@ def mock_all(
|
||||
addon_info: AsyncMock,
|
||||
addon_stats: AsyncMock,
|
||||
addon_changelog: AsyncMock,
|
||||
resolution_info: AsyncMock,
|
||||
) -> None:
|
||||
"""Mock all setup requests."""
|
||||
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
|
||||
@ -204,19 +205,6 @@ def mock_all(
|
||||
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": [],
|
||||
},
|
||||
},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/network/info",
|
||||
json={
|
||||
|
@ -4,11 +4,28 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from datetime import timedelta
|
||||
from http import HTTPStatus
|
||||
import os
|
||||
from typing import Any
|
||||
from unittest.mock import ANY, patch
|
||||
from unittest.mock import ANY, AsyncMock, patch
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from aiohasupervisor import (
|
||||
SupervisorBadRequestError,
|
||||
SupervisorError,
|
||||
SupervisorTimeoutError,
|
||||
)
|
||||
from aiohasupervisor.models import (
|
||||
Check,
|
||||
CheckType,
|
||||
ContextType,
|
||||
Issue,
|
||||
IssueType,
|
||||
ResolutionInfo,
|
||||
Suggestion,
|
||||
SuggestionType,
|
||||
UnhealthyReason,
|
||||
UnsupportedReason,
|
||||
)
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
@ -18,7 +35,6 @@ from homeassistant.setup import async_setup_component
|
||||
|
||||
from .test_init import MOCK_ENVIRON
|
||||
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker, AiohttpClientMockResponse
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
@ -36,49 +52,41 @@ def fixture_supervisor_environ() -> Generator[None]:
|
||||
|
||||
|
||||
def mock_resolution_info(
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
unsupported: list[str] | None = None,
|
||||
unhealthy: list[str] | None = None,
|
||||
issues: list[dict[str, str]] | None = None,
|
||||
suggestion_result: str = "ok",
|
||||
supervisor_client: AsyncMock,
|
||||
unsupported: list[UnsupportedReason] | None = None,
|
||||
unhealthy: list[UnhealthyReason] | None = None,
|
||||
issues: list[Issue] | None = None,
|
||||
suggestions_by_issue: dict[UUID, list[Suggestion]] | None = None,
|
||||
suggestion_result: SupervisorError | None = None,
|
||||
) -> None:
|
||||
"""Mock resolution/info endpoint with unsupported/unhealthy reasons and/or issues."""
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/resolution/info",
|
||||
json={
|
||||
"result": "ok",
|
||||
"data": {
|
||||
"unsupported": unsupported or [],
|
||||
"unhealthy": unhealthy or [],
|
||||
"suggestions": [],
|
||||
"issues": [
|
||||
{k: v for k, v in issue.items() if k != "suggestions"}
|
||||
for issue in issues
|
||||
]
|
||||
if issues
|
||||
else [],
|
||||
"checks": [
|
||||
{"enabled": True, "slug": "supervisor_trust"},
|
||||
{"enabled": True, "slug": "free_space"},
|
||||
],
|
||||
},
|
||||
},
|
||||
supervisor_client.resolution.info.return_value = ResolutionInfo(
|
||||
unsupported=unsupported or [],
|
||||
unhealthy=unhealthy or [],
|
||||
issues=issues or [],
|
||||
suggestions=[
|
||||
suggestion
|
||||
for issue_list in suggestions_by_issue.values()
|
||||
for suggestion in issue_list
|
||||
]
|
||||
if suggestions_by_issue
|
||||
else [],
|
||||
checks=[
|
||||
Check(enabled=True, slug=CheckType.SUPERVISOR_TRUST),
|
||||
Check(enabled=True, slug=CheckType.FREE_SPACE),
|
||||
],
|
||||
)
|
||||
|
||||
if issues:
|
||||
suggestions_by_issue = {
|
||||
issue["uuid"]: issue.get("suggestions", []) for issue in issues
|
||||
}
|
||||
for issue_uuid, suggestions in suggestions_by_issue.items():
|
||||
aioclient_mock.get(
|
||||
f"http://127.0.0.1/resolution/issue/{issue_uuid}/suggestions",
|
||||
json={"result": "ok", "data": {"suggestions": suggestions}},
|
||||
)
|
||||
for suggestion in suggestions:
|
||||
aioclient_mock.post(
|
||||
f"http://127.0.0.1/resolution/suggestion/{suggestion['uuid']}",
|
||||
json={"result": suggestion_result},
|
||||
)
|
||||
if suggestions_by_issue:
|
||||
|
||||
async def mock_suggestions_for_issue(uuid: UUID) -> list[Suggestion]:
|
||||
"""Mock of suggestions for issue api."""
|
||||
return suggestions_by_issue.get(uuid, [])
|
||||
|
||||
supervisor_client.resolution.suggestions_for_issue.side_effect = (
|
||||
mock_suggestions_for_issue
|
||||
)
|
||||
supervisor_client.resolution.apply_suggestion.side_effect = suggestion_result
|
||||
|
||||
|
||||
def assert_repair_in_list(
|
||||
@ -134,11 +142,13 @@ def assert_issue_repair_in_list(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_unhealthy_issues(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test issues added for unhealthy systems."""
|
||||
mock_resolution_info(aioclient_mock, unhealthy=["docker", "setup"])
|
||||
mock_resolution_info(
|
||||
supervisor_client, unhealthy=[UnhealthyReason.DOCKER, UnhealthyReason.SETUP]
|
||||
)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
assert result
|
||||
@ -156,11 +166,14 @@ async def test_unhealthy_issues(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_unsupported_issues(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test issues added for unsupported systems."""
|
||||
mock_resolution_info(aioclient_mock, unsupported=["content_trust", "os"])
|
||||
mock_resolution_info(
|
||||
supervisor_client,
|
||||
unsupported=[UnsupportedReason.CONTENT_TRUST, UnsupportedReason.OS],
|
||||
)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
assert result
|
||||
@ -180,11 +193,11 @@ async def test_unsupported_issues(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_unhealthy_issues_add_remove(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test unhealthy issues added and removed from dispatches."""
|
||||
mock_resolution_info(aioclient_mock)
|
||||
mock_resolution_info(supervisor_client)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
assert result
|
||||
@ -237,11 +250,11 @@ async def test_unhealthy_issues_add_remove(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_unsupported_issues_add_remove(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test unsupported issues added and removed from dispatches."""
|
||||
mock_resolution_info(aioclient_mock)
|
||||
mock_resolution_info(supervisor_client)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
assert result
|
||||
@ -294,21 +307,21 @@ async def test_unsupported_issues_add_remove(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_reset_issues_supervisor_restart(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""All issues reset on supervisor restart."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
unsupported=["os"],
|
||||
unhealthy=["docker"],
|
||||
supervisor_client,
|
||||
unsupported=[UnsupportedReason.OS],
|
||||
unhealthy=[UnhealthyReason.DOCKER],
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "reboot_required",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
}
|
||||
Issue(
|
||||
type=IssueType.REBOOT_REQUIRED,
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=(uuid := uuid4()),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
@ -325,15 +338,14 @@ async def test_reset_issues_supervisor_restart(
|
||||
assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")
|
||||
assert_issue_repair_in_list(
|
||||
msg["result"]["issues"],
|
||||
uuid="1234",
|
||||
uuid=uuid.hex,
|
||||
context="system",
|
||||
type_="reboot_required",
|
||||
fixable=False,
|
||||
reference=None,
|
||||
)
|
||||
|
||||
aioclient_mock.clear_requests()
|
||||
mock_resolution_info(aioclient_mock)
|
||||
mock_resolution_info(supervisor_client)
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 2,
|
||||
@ -358,11 +370,15 @@ async def test_reset_issues_supervisor_restart(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_reasons_added_and_removed(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test an unsupported/unhealthy reasons being added and removed at same time."""
|
||||
mock_resolution_info(aioclient_mock, unsupported=["os"], unhealthy=["docker"])
|
||||
mock_resolution_info(
|
||||
supervisor_client,
|
||||
unsupported=[UnsupportedReason.OS],
|
||||
unhealthy=[UnhealthyReason.DOCKER],
|
||||
)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
assert result
|
||||
@ -376,9 +392,10 @@ async def test_reasons_added_and_removed(
|
||||
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"]
|
||||
supervisor_client,
|
||||
unsupported=[UnsupportedReason.CONTENT_TRUST],
|
||||
unhealthy=[UnhealthyReason.SETUP],
|
||||
)
|
||||
await client.send_json(
|
||||
{
|
||||
@ -408,12 +425,14 @@ async def test_reasons_added_and_removed(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_ignored_unsupported_skipped(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Unsupported reasons which have an identical unhealthy reason are ignored."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock, unsupported=["privileged"], unhealthy=["privileged"]
|
||||
supervisor_client,
|
||||
unsupported=[UnsupportedReason.PRIVILEGED],
|
||||
unhealthy=[UnhealthyReason.PRIVILEGED],
|
||||
)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
@ -431,12 +450,14 @@ async def test_ignored_unsupported_skipped(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_new_unsupported_unhealthy_reason(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""New unsupported/unhealthy reasons result in a generic repair until next core update."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock, unsupported=["fake_unsupported"], unhealthy=["fake_unhealthy"]
|
||||
supervisor_client,
|
||||
unsupported=["fake_unsupported"],
|
||||
unhealthy=["fake_unhealthy"],
|
||||
)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
@ -481,40 +502,43 @@ async def test_new_unsupported_unhealthy_reason(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issues(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test repairs added for supervisor issue."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "reboot_required",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
},
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "multiple_data_disks",
|
||||
"context": "system",
|
||||
"reference": "/dev/sda1",
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1236",
|
||||
"type": "rename_data_disk",
|
||||
"context": "system",
|
||||
"reference": "/dev/sda1",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"uuid": "1237",
|
||||
"type": "should_not_be_repair",
|
||||
"context": "os",
|
||||
"reference": None,
|
||||
},
|
||||
Issue(
|
||||
type=IssueType.REBOOT_REQUIRED,
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=(uuid_issue1 := uuid4()),
|
||||
),
|
||||
Issue(
|
||||
type=IssueType.MULTIPLE_DATA_DISKS,
|
||||
context=ContextType.SYSTEM,
|
||||
reference="/dev/sda1",
|
||||
uuid=(uuid_issue2 := uuid4()),
|
||||
),
|
||||
Issue(
|
||||
type="should_not_be_repair",
|
||||
context=ContextType.OS,
|
||||
reference=None,
|
||||
uuid=uuid4(),
|
||||
),
|
||||
],
|
||||
suggestions_by_issue={
|
||||
uuid_issue2: [
|
||||
Suggestion(
|
||||
type=SuggestionType.RENAME_DATA_DISK,
|
||||
context=ContextType.SYSTEM,
|
||||
reference="/dev/sda1",
|
||||
uuid=uuid4(),
|
||||
auto=False,
|
||||
)
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
@ -528,7 +552,7 @@ async def test_supervisor_issues(
|
||||
assert len(msg["result"]["issues"]) == 2
|
||||
assert_issue_repair_in_list(
|
||||
msg["result"]["issues"],
|
||||
uuid="1234",
|
||||
uuid=uuid_issue1.hex,
|
||||
context="system",
|
||||
type_="reboot_required",
|
||||
fixable=False,
|
||||
@ -536,7 +560,7 @@ async def test_supervisor_issues(
|
||||
)
|
||||
assert_issue_repair_in_list(
|
||||
msg["result"]["issues"],
|
||||
uuid="1235",
|
||||
uuid=uuid_issue2.hex,
|
||||
context="system",
|
||||
type_="multiple_data_disks",
|
||||
fixable=True,
|
||||
@ -547,61 +571,33 @@ async def test_supervisor_issues(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issues_initial_failure(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
resolution_info: AsyncMock,
|
||||
resolution_suggestions_for_issue: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test issues manager retries after initial update failure."""
|
||||
responses = [
|
||||
AiohttpClientMockResponse(
|
||||
method="get",
|
||||
url="http://127.0.0.1/resolution/info",
|
||||
status=HTTPStatus.BAD_REQUEST,
|
||||
json={
|
||||
"result": "error",
|
||||
"message": "System is not ready with state: setup",
|
||||
},
|
||||
),
|
||||
AiohttpClientMockResponse(
|
||||
method="get",
|
||||
url="http://127.0.0.1/resolution/info",
|
||||
status=HTTPStatus.OK,
|
||||
json={
|
||||
"result": "ok",
|
||||
"data": {
|
||||
"unsupported": [],
|
||||
"unhealthy": [],
|
||||
"suggestions": [],
|
||||
"issues": [
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "reboot_required",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
},
|
||||
],
|
||||
"checks": [
|
||||
{"enabled": True, "slug": "supervisor_trust"},
|
||||
{"enabled": True, "slug": "free_space"},
|
||||
],
|
||||
},
|
||||
},
|
||||
resolution_info.side_effect = [
|
||||
SupervisorBadRequestError("System is not ready with state: setup"),
|
||||
ResolutionInfo(
|
||||
unsupported=[],
|
||||
unhealthy=[],
|
||||
suggestions=[],
|
||||
issues=[
|
||||
Issue(
|
||||
type=IssueType.REBOOT_REQUIRED,
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=uuid4(),
|
||||
)
|
||||
],
|
||||
checks=[
|
||||
Check(enabled=True, slug=CheckType.SUPERVISOR_TRUST),
|
||||
Check(enabled=True, slug=CheckType.FREE_SPACE),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
async def mock_responses(*args):
|
||||
nonlocal responses
|
||||
return responses.pop(0)
|
||||
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/resolution/info",
|
||||
side_effect=mock_responses,
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/resolution/issue/1234/suggestions",
|
||||
json={"result": "ok", "data": {"suggestions": []}},
|
||||
)
|
||||
|
||||
with patch("homeassistant.components.hassio.issues.REQUEST_REFRESH_DELAY", new=0.1):
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
await hass.async_block_till_done()
|
||||
@ -625,11 +621,11 @@ async def test_supervisor_issues_initial_failure(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issues_add_remove(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test supervisor issues added and removed from dispatches."""
|
||||
mock_resolution_info(aioclient_mock)
|
||||
mock_resolution_info(supervisor_client)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
assert result
|
||||
@ -643,7 +639,7 @@ async def test_supervisor_issues_add_remove(
|
||||
"data": {
|
||||
"event": "issue_changed",
|
||||
"data": {
|
||||
"uuid": "1234",
|
||||
"uuid": (issue_uuid := uuid4().hex),
|
||||
"type": "reboot_required",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
@ -661,7 +657,7 @@ async def test_supervisor_issues_add_remove(
|
||||
assert len(msg["result"]["issues"]) == 1
|
||||
assert_issue_repair_in_list(
|
||||
msg["result"]["issues"],
|
||||
uuid="1234",
|
||||
uuid=issue_uuid,
|
||||
context="system",
|
||||
type_="reboot_required",
|
||||
fixable=False,
|
||||
@ -675,13 +671,13 @@ async def test_supervisor_issues_add_remove(
|
||||
"data": {
|
||||
"event": "issue_changed",
|
||||
"data": {
|
||||
"uuid": "1234",
|
||||
"uuid": issue_uuid,
|
||||
"type": "reboot_required",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"uuid": uuid4().hex,
|
||||
"type": "execute_reboot",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
@ -701,7 +697,7 @@ async def test_supervisor_issues_add_remove(
|
||||
assert len(msg["result"]["issues"]) == 1
|
||||
assert_issue_repair_in_list(
|
||||
msg["result"]["issues"],
|
||||
uuid="1234",
|
||||
uuid=issue_uuid,
|
||||
context="system",
|
||||
type_="reboot_required",
|
||||
fixable=True,
|
||||
@ -715,7 +711,7 @@ async def test_supervisor_issues_add_remove(
|
||||
"data": {
|
||||
"event": "issue_removed",
|
||||
"data": {
|
||||
"uuid": "1234",
|
||||
"uuid": issue_uuid,
|
||||
"type": "reboot_required",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
@ -736,37 +732,23 @@ async def test_supervisor_issues_add_remove(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issues_suggestions_fail(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
resolution_suggestions_for_issue: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test failing to get suggestions for issue skips it."""
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/resolution/info",
|
||||
json={
|
||||
"result": "ok",
|
||||
"data": {
|
||||
"unsupported": [],
|
||||
"unhealthy": [],
|
||||
"suggestions": [],
|
||||
"issues": [
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "reboot_required",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
}
|
||||
],
|
||||
"checks": [
|
||||
{"enabled": True, "slug": "supervisor_trust"},
|
||||
{"enabled": True, "slug": "free_space"},
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/resolution/issue/1234/suggestions",
|
||||
exc=TimeoutError(),
|
||||
mock_resolution_info(
|
||||
supervisor_client,
|
||||
issues=[
|
||||
Issue(
|
||||
type=IssueType.REBOOT_REQUIRED,
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=uuid4(),
|
||||
)
|
||||
],
|
||||
)
|
||||
resolution_suggestions_for_issue.side_effect = SupervisorTimeoutError
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
assert result
|
||||
@ -782,11 +764,11 @@ async def test_supervisor_issues_suggestions_fail(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_remove_missing_issue_without_error(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test HA skips message to remove issue that it didn't know about (sync issue)."""
|
||||
mock_resolution_info(aioclient_mock)
|
||||
mock_resolution_info(supervisor_client)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
assert result
|
||||
@ -816,16 +798,12 @@ async def test_supervisor_remove_missing_issue_without_error(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_system_is_not_ready(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
resolution_info: AsyncMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Ensure hassio starts despite error."""
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/resolution/info",
|
||||
json={
|
||||
"result": "",
|
||||
"message": "System is not ready with state: setup",
|
||||
},
|
||||
resolution_info.side_effect = SupervisorBadRequestError(
|
||||
"System is not ready with state: setup"
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
@ -838,11 +816,11 @@ async def test_system_is_not_ready(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issues_detached_addon_missing(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test supervisor issue for detached addon due to missing repository."""
|
||||
mock_resolution_info(aioclient_mock)
|
||||
mock_resolution_info(supervisor_client)
|
||||
|
||||
result = await async_setup_component(hass, "hassio", {})
|
||||
assert result
|
||||
@ -856,7 +834,7 @@ async def test_supervisor_issues_detached_addon_missing(
|
||||
"data": {
|
||||
"event": "issue_changed",
|
||||
"data": {
|
||||
"uuid": "1234",
|
||||
"uuid": (issue_uuid := uuid4().hex),
|
||||
"type": "detached_addon_missing",
|
||||
"context": "addon",
|
||||
"reference": "test",
|
||||
@ -874,7 +852,7 @@ async def test_supervisor_issues_detached_addon_missing(
|
||||
assert len(msg["result"]["issues"]) == 1
|
||||
assert_issue_repair_in_list(
|
||||
msg["result"]["issues"],
|
||||
uuid="1234",
|
||||
uuid=issue_uuid,
|
||||
context="addon",
|
||||
type_="detached_addon_missing",
|
||||
fixable=False,
|
||||
|
@ -3,8 +3,17 @@
|
||||
from collections.abc import Generator
|
||||
from http import HTTPStatus
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
from aiohasupervisor import SupervisorError
|
||||
from aiohasupervisor.models import (
|
||||
ContextType,
|
||||
Issue,
|
||||
IssueType,
|
||||
Suggestion,
|
||||
SuggestionType,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -14,7 +23,6 @@ from homeassistant.setup import async_setup_component
|
||||
from .test_init import MOCK_ENVIRON
|
||||
from .test_issues import mock_resolution_info
|
||||
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
@ -28,34 +36,39 @@ def fixture_supervisor_environ() -> Generator[None]:
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issue_repair_flow(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test fix flow for supervisor issue."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "multiple_data_disks",
|
||||
"context": "system",
|
||||
"reference": "/dev/sda1",
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "rename_data_disk",
|
||||
"context": "system",
|
||||
"reference": "/dev/sda1",
|
||||
}
|
||||
],
|
||||
},
|
||||
Issue(
|
||||
type=IssueType.MULTIPLE_DATA_DISKS,
|
||||
context=ContextType.SYSTEM,
|
||||
reference="/dev/sda1",
|
||||
uuid=(issue_uuid := uuid4()),
|
||||
),
|
||||
],
|
||||
suggestions_by_issue={
|
||||
issue_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.RENAME_DATA_DISK,
|
||||
context=ContextType.SYSTEM,
|
||||
reference="/dev/sda1",
|
||||
uuid=(sugg_uuid := uuid4()),
|
||||
auto=False,
|
||||
)
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
|
||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
repair_issue = issue_registry.async_get_issue(
|
||||
domain="hassio", issue_id=issue_uuid.hex
|
||||
)
|
||||
assert repair_issue
|
||||
|
||||
client = await hass_client()
|
||||
@ -95,52 +108,53 @@ async def test_supervisor_issue_repair_flow(
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][0] == "post"
|
||||
assert (
|
||||
str(aioclient_mock.mock_calls[-1][1])
|
||||
== "http://127.0.0.1/resolution/suggestion/1235"
|
||||
)
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
|
||||
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issue_repair_flow_with_multiple_suggestions(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test fix flow for supervisor issue with multiple suggestions."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "reboot_required",
|
||||
"context": "system",
|
||||
"reference": "test",
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "execute_reboot",
|
||||
"context": "system",
|
||||
"reference": "test",
|
||||
},
|
||||
{
|
||||
"uuid": "1236",
|
||||
"type": "test_type",
|
||||
"context": "system",
|
||||
"reference": "test",
|
||||
},
|
||||
],
|
||||
},
|
||||
Issue(
|
||||
type=IssueType.REBOOT_REQUIRED,
|
||||
context=ContextType.SYSTEM,
|
||||
reference="test",
|
||||
uuid=(issue_uuid := uuid4()),
|
||||
),
|
||||
],
|
||||
suggestions_by_issue={
|
||||
issue_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_REBOOT,
|
||||
context=ContextType.SYSTEM,
|
||||
reference="test",
|
||||
uuid=uuid4(),
|
||||
auto=False,
|
||||
),
|
||||
Suggestion(
|
||||
type="test_type",
|
||||
context=ContextType.SYSTEM,
|
||||
reference="test",
|
||||
uuid=(sugg_uuid := uuid4()),
|
||||
auto=False,
|
||||
),
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
|
||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
repair_issue = issue_registry.async_get_issue(
|
||||
domain="hassio", issue_id=issue_uuid.hex
|
||||
)
|
||||
assert repair_issue
|
||||
|
||||
client = await hass_client()
|
||||
@ -189,52 +203,53 @@ async def test_supervisor_issue_repair_flow_with_multiple_suggestions(
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][0] == "post"
|
||||
assert (
|
||||
str(aioclient_mock.mock_calls[-1][1])
|
||||
== "http://127.0.0.1/resolution/suggestion/1236"
|
||||
)
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
|
||||
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issue_repair_flow_with_multiple_suggestions_and_confirmation(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test fix flow for supervisor issue with multiple suggestions and choice requires confirmation."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "reboot_required",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "execute_reboot",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
},
|
||||
{
|
||||
"uuid": "1236",
|
||||
"type": "test_type",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
Issue(
|
||||
type=IssueType.REBOOT_REQUIRED,
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=(issue_uuid := uuid4()),
|
||||
),
|
||||
],
|
||||
suggestions_by_issue={
|
||||
issue_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_REBOOT,
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=(sugg_uuid := uuid4()),
|
||||
auto=False,
|
||||
),
|
||||
Suggestion(
|
||||
type="test_type",
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=uuid4(),
|
||||
auto=False,
|
||||
),
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
|
||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
repair_issue = issue_registry.async_get_issue(
|
||||
domain="hassio", issue_id=issue_uuid.hex
|
||||
)
|
||||
assert repair_issue
|
||||
|
||||
client = await hass_client()
|
||||
@ -302,46 +317,46 @@ async def test_supervisor_issue_repair_flow_with_multiple_suggestions_and_confir
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][0] == "post"
|
||||
assert (
|
||||
str(aioclient_mock.mock_calls[-1][1])
|
||||
== "http://127.0.0.1/resolution/suggestion/1235"
|
||||
)
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
|
||||
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issue_repair_flow_skip_confirmation(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test confirmation skipped for fix flow for supervisor issue with one suggestion."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "reboot_required",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "execute_reboot",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
}
|
||||
],
|
||||
},
|
||||
Issue(
|
||||
type=IssueType.REBOOT_REQUIRED,
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=(issue_uuid := uuid4()),
|
||||
),
|
||||
],
|
||||
suggestions_by_issue={
|
||||
issue_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_REBOOT,
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=(sugg_uuid := uuid4()),
|
||||
auto=False,
|
||||
),
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
|
||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
repair_issue = issue_registry.async_get_issue(
|
||||
domain="hassio", issue_id=issue_uuid.hex
|
||||
)
|
||||
assert repair_issue
|
||||
|
||||
client = await hass_client()
|
||||
@ -381,53 +396,54 @@ async def test_supervisor_issue_repair_flow_skip_confirmation(
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][0] == "post"
|
||||
assert (
|
||||
str(aioclient_mock.mock_calls[-1][1])
|
||||
== "http://127.0.0.1/resolution/suggestion/1235"
|
||||
)
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
|
||||
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_mount_failed_repair_flow_error(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test repair flow fails when repair fails to apply."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "mount_failed",
|
||||
"context": "mount",
|
||||
"reference": "backup_share",
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "execute_reload",
|
||||
"context": "mount",
|
||||
"reference": "backup_share",
|
||||
},
|
||||
{
|
||||
"uuid": "1236",
|
||||
"type": "execute_remove",
|
||||
"context": "mount",
|
||||
"reference": "backup_share",
|
||||
},
|
||||
],
|
||||
},
|
||||
Issue(
|
||||
type=IssueType.MOUNT_FAILED,
|
||||
context=ContextType.MOUNT,
|
||||
reference="backup_share",
|
||||
uuid=(issue_uuid := uuid4()),
|
||||
),
|
||||
],
|
||||
suggestion_result=False,
|
||||
suggestions_by_issue={
|
||||
issue_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_RELOAD,
|
||||
context=ContextType.MOUNT,
|
||||
reference="backup_share",
|
||||
uuid=uuid4(),
|
||||
auto=False,
|
||||
),
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_REMOVE,
|
||||
context=ContextType.MOUNT,
|
||||
reference="backup_share",
|
||||
uuid=uuid4(),
|
||||
auto=False,
|
||||
),
|
||||
]
|
||||
},
|
||||
suggestion_result=SupervisorError("boom"),
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
|
||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
repair_issue = issue_registry.async_get_issue(
|
||||
domain="hassio", issue_id=issue_uuid.hex
|
||||
)
|
||||
assert repair_issue
|
||||
|
||||
client = await hass_client()
|
||||
@ -459,46 +475,52 @@ async def test_mount_failed_repair_flow_error(
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
assert issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
assert issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_mount_failed_repair_flow(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test repair flow for mount_failed issue."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "mount_failed",
|
||||
"context": "mount",
|
||||
"reference": "backup_share",
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "execute_reload",
|
||||
"context": "mount",
|
||||
"reference": "backup_share",
|
||||
},
|
||||
{
|
||||
"uuid": "1236",
|
||||
"type": "execute_remove",
|
||||
"context": "mount",
|
||||
"reference": "backup_share",
|
||||
},
|
||||
],
|
||||
},
|
||||
Issue(
|
||||
type=IssueType.MOUNT_FAILED,
|
||||
context=ContextType.MOUNT,
|
||||
reference="backup_share",
|
||||
uuid=(issue_uuid := uuid4()),
|
||||
),
|
||||
],
|
||||
suggestions_by_issue={
|
||||
issue_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_RELOAD,
|
||||
context=ContextType.MOUNT,
|
||||
reference="backup_share",
|
||||
uuid=(sugg_uuid := uuid4()),
|
||||
auto=False,
|
||||
),
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_REMOVE,
|
||||
context=ContextType.MOUNT,
|
||||
reference="backup_share",
|
||||
uuid=uuid4(),
|
||||
auto=False,
|
||||
),
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
|
||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
repair_issue = issue_registry.async_get_issue(
|
||||
domain="hassio", issue_id=issue_uuid.hex
|
||||
)
|
||||
assert repair_issue
|
||||
|
||||
client = await hass_client()
|
||||
@ -551,13 +573,8 @@ async def test_mount_failed_repair_flow(
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][0] == "post"
|
||||
assert (
|
||||
str(aioclient_mock.mock_calls[-1][1])
|
||||
== "http://127.0.0.1/resolution/suggestion/1235"
|
||||
)
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
|
||||
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -566,62 +583,69 @@ async def test_mount_failed_repair_flow(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issue_docker_config_repair_flow(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test fix flow for supervisor issue."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "docker_config",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "execute_rebuild",
|
||||
"context": "system",
|
||||
"reference": None,
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"uuid": "1236",
|
||||
"type": "docker_config",
|
||||
"context": "core",
|
||||
"reference": None,
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1237",
|
||||
"type": "execute_rebuild",
|
||||
"context": "core",
|
||||
"reference": None,
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"uuid": "1238",
|
||||
"type": "docker_config",
|
||||
"context": "addon",
|
||||
"reference": "test",
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1239",
|
||||
"type": "execute_rebuild",
|
||||
"context": "addon",
|
||||
"reference": "test",
|
||||
}
|
||||
],
|
||||
},
|
||||
Issue(
|
||||
type=IssueType.DOCKER_CONFIG,
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=(issue1_uuid := uuid4()),
|
||||
),
|
||||
Issue(
|
||||
type=IssueType.DOCKER_CONFIG,
|
||||
context=ContextType.CORE,
|
||||
reference=None,
|
||||
uuid=(issue2_uuid := uuid4()),
|
||||
),
|
||||
Issue(
|
||||
type=IssueType.DOCKER_CONFIG,
|
||||
context=ContextType.ADDON,
|
||||
reference="test",
|
||||
uuid=(issue3_uuid := uuid4()),
|
||||
),
|
||||
],
|
||||
suggestions_by_issue={
|
||||
issue1_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_REBUILD,
|
||||
context=ContextType.SYSTEM,
|
||||
reference=None,
|
||||
uuid=(sugg_uuid := uuid4()),
|
||||
auto=False,
|
||||
),
|
||||
],
|
||||
issue2_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_REBUILD,
|
||||
context=ContextType.CORE,
|
||||
reference=None,
|
||||
uuid=uuid4(),
|
||||
auto=False,
|
||||
),
|
||||
],
|
||||
issue3_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_REBUILD,
|
||||
context=ContextType.ADDON,
|
||||
reference="test",
|
||||
uuid=uuid4(),
|
||||
auto=False,
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
|
||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
repair_issue = issue_registry.async_get_issue(
|
||||
domain="hassio", issue_id=issue1_uuid.hex
|
||||
)
|
||||
assert repair_issue
|
||||
|
||||
client = await hass_client()
|
||||
@ -661,52 +685,53 @@ async def test_supervisor_issue_docker_config_repair_flow(
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][0] == "post"
|
||||
assert (
|
||||
str(aioclient_mock.mock_calls[-1][1])
|
||||
== "http://127.0.0.1/resolution/suggestion/1235"
|
||||
)
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id=issue1_uuid.hex)
|
||||
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issue_repair_flow_multiple_data_disks(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test fix flow for multiple data disks supervisor issue."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "multiple_data_disks",
|
||||
"context": "system",
|
||||
"reference": "/dev/sda1",
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "rename_data_disk",
|
||||
"context": "system",
|
||||
"reference": "/dev/sda1",
|
||||
},
|
||||
{
|
||||
"uuid": "1236",
|
||||
"type": "adopt_data_disk",
|
||||
"context": "system",
|
||||
"reference": "/dev/sda1",
|
||||
},
|
||||
],
|
||||
},
|
||||
Issue(
|
||||
type=IssueType.MULTIPLE_DATA_DISKS,
|
||||
context=ContextType.SYSTEM,
|
||||
reference="/dev/sda1",
|
||||
uuid=(issue_uuid := uuid4()),
|
||||
),
|
||||
],
|
||||
suggestions_by_issue={
|
||||
issue_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.RENAME_DATA_DISK,
|
||||
context=ContextType.SYSTEM,
|
||||
reference="/dev/sda1",
|
||||
uuid=uuid4(),
|
||||
auto=False,
|
||||
),
|
||||
Suggestion(
|
||||
type=SuggestionType.ADOPT_DATA_DISK,
|
||||
context=ContextType.SYSTEM,
|
||||
reference="/dev/sda1",
|
||||
uuid=(sugg_uuid := uuid4()),
|
||||
auto=False,
|
||||
),
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
|
||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
repair_issue = issue_registry.async_get_issue(
|
||||
domain="hassio", issue_id=issue_uuid.hex
|
||||
)
|
||||
assert repair_issue
|
||||
|
||||
client = await hass_client()
|
||||
@ -774,13 +799,8 @@ async def test_supervisor_issue_repair_flow_multiple_data_disks(
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][0] == "post"
|
||||
assert (
|
||||
str(aioclient_mock.mock_calls[-1][1])
|
||||
== "http://127.0.0.1/resolution/suggestion/1236"
|
||||
)
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
|
||||
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -789,34 +809,39 @@ async def test_supervisor_issue_repair_flow_multiple_data_disks(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issue_detached_addon_removed(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test fix flow for supervisor issue."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "detached_addon_removed",
|
||||
"context": "addon",
|
||||
"reference": "test",
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "execute_remove",
|
||||
"context": "addon",
|
||||
"reference": "test",
|
||||
}
|
||||
],
|
||||
},
|
||||
Issue(
|
||||
type=IssueType.DETACHED_ADDON_REMOVED,
|
||||
context=ContextType.ADDON,
|
||||
reference="test",
|
||||
uuid=(issue_uuid := uuid4()),
|
||||
),
|
||||
],
|
||||
suggestions_by_issue={
|
||||
issue_uuid: [
|
||||
Suggestion(
|
||||
type=SuggestionType.EXECUTE_REMOVE,
|
||||
context=ContextType.ADDON,
|
||||
reference="test",
|
||||
uuid=(sugg_uuid := uuid4()),
|
||||
auto=False,
|
||||
),
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
|
||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
repair_issue = issue_registry.async_get_issue(
|
||||
domain="hassio", issue_id=issue_uuid.hex
|
||||
)
|
||||
assert repair_issue
|
||||
|
||||
client = await hass_client()
|
||||
@ -861,13 +886,8 @@ async def test_supervisor_issue_detached_addon_removed(
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][0] == "post"
|
||||
assert (
|
||||
str(aioclient_mock.mock_calls[-1][1])
|
||||
== "http://127.0.0.1/resolution/suggestion/1235"
|
||||
)
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
|
||||
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -876,40 +896,46 @@ async def test_supervisor_issue_detached_addon_removed(
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_supervisor_issue_addon_boot_fail(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_client: AsyncMock,
|
||||
hass_client: ClientSessionGenerator,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test fix flow for supervisor issue."""
|
||||
mock_resolution_info(
|
||||
aioclient_mock,
|
||||
supervisor_client,
|
||||
issues=[
|
||||
{
|
||||
"uuid": "1234",
|
||||
"type": "boot_fail",
|
||||
"context": "addon",
|
||||
"reference": "test",
|
||||
"suggestions": [
|
||||
{
|
||||
"uuid": "1235",
|
||||
"type": "execute_start",
|
||||
"context": "addon",
|
||||
"reference": "test",
|
||||
},
|
||||
{
|
||||
"uuid": "1236",
|
||||
"type": "disable_boot",
|
||||
"context": "addon",
|
||||
"reference": "test",
|
||||
},
|
||||
],
|
||||
},
|
||||
Issue(
|
||||
type="boot_fail",
|
||||
context=ContextType.ADDON,
|
||||
reference="test",
|
||||
uuid=(issue_uuid := uuid4()),
|
||||
),
|
||||
],
|
||||
suggestions_by_issue={
|
||||
issue_uuid: [
|
||||
Suggestion(
|
||||
type="execute_start",
|
||||
context=ContextType.ADDON,
|
||||
reference="test",
|
||||
uuid=(sugg_uuid := uuid4()),
|
||||
auto=False,
|
||||
),
|
||||
Suggestion(
|
||||
type="disable_boot",
|
||||
context=ContextType.ADDON,
|
||||
reference="test",
|
||||
uuid=uuid4(),
|
||||
auto=False,
|
||||
),
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "hassio", {})
|
||||
|
||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
repair_issue = issue_registry.async_get_issue(
|
||||
domain="hassio", issue_id=issue_uuid.hex
|
||||
)
|
||||
assert repair_issue
|
||||
|
||||
client = await hass_client()
|
||||
@ -962,10 +988,5 @@ async def test_supervisor_issue_addon_boot_fail(
|
||||
"description_placeholders": None,
|
||||
}
|
||||
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||
|
||||
assert aioclient_mock.mock_calls[-1][0] == "post"
|
||||
assert (
|
||||
str(aioclient_mock.mock_calls[-1][1])
|
||||
== "http://127.0.0.1/resolution/suggestion/1235"
|
||||
)
|
||||
assert not issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
|
||||
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
|
||||
|
@ -33,6 +33,7 @@ def mock_all(
|
||||
store_info: AsyncMock,
|
||||
addon_stats: AsyncMock,
|
||||
addon_changelog: AsyncMock,
|
||||
resolution_info: AsyncMock,
|
||||
) -> None:
|
||||
"""Mock all setup requests."""
|
||||
_install_default_mocks(aioclient_mock)
|
||||
@ -146,19 +147,6 @@ def _install_default_mocks(aioclient_mock: AiohttpClientMocker):
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/resolution/info",
|
||||
json={
|
||||
"result": "ok",
|
||||
"data": {
|
||||
"unsupported": [],
|
||||
"unhealthy": [],
|
||||
"suggestions": [],
|
||||
"issues": [],
|
||||
"checks": [],
|
||||
},
|
||||
},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/network/info",
|
||||
json={
|
||||
|
@ -29,6 +29,7 @@ def mock_all(
|
||||
store_info: AsyncMock,
|
||||
addon_stats: AsyncMock,
|
||||
addon_changelog: AsyncMock,
|
||||
resolution_info: AsyncMock,
|
||||
) -> None:
|
||||
"""Mock all setup requests."""
|
||||
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
|
||||
@ -149,19 +150,6 @@ def mock_all(
|
||||
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": [],
|
||||
},
|
||||
},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/network/info",
|
||||
json={
|
||||
|
@ -26,7 +26,9 @@ from tests.typing import WebSocketGenerator
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_all(
|
||||
aioclient_mock: AiohttpClientMocker, supervisor_is_connected: AsyncMock
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
supervisor_is_connected: AsyncMock,
|
||||
resolution_info: AsyncMock,
|
||||
) -> None:
|
||||
"""Mock all setup requests."""
|
||||
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
|
||||
@ -67,19 +69,6 @@ def mock_all(
|
||||
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": [],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("hassio_env")
|
||||
|
@ -197,6 +197,7 @@ async def test_access_from_supervisor_ip(
|
||||
hass: HomeAssistant,
|
||||
aiohttp_client: ClientSessionGenerator,
|
||||
hassio_env,
|
||||
resolution_info: AsyncMock,
|
||||
) -> None:
|
||||
"""Test accessing to server from supervisor IP."""
|
||||
app = web.Application()
|
||||
@ -218,17 +219,7 @@ async def test_access_from_supervisor_ip(
|
||||
|
||||
manager = app[KEY_BAN_MANAGER]
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hassio.HassIO.get_resolution_info",
|
||||
return_value={
|
||||
"unsupported": [],
|
||||
"unhealthy": [],
|
||||
"suggestions": [],
|
||||
"issues": [],
|
||||
"checks": [],
|
||||
},
|
||||
):
|
||||
assert await async_setup_component(hass, "hassio", {"hassio": {}})
|
||||
assert await async_setup_component(hass, "hassio", {"hassio": {}})
|
||||
|
||||
m_open = mock_open()
|
||||
|
||||
|
@ -72,23 +72,11 @@ async def mock_supervisor_fixture(
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
store_info: AsyncMock,
|
||||
supervisor_is_connected: AsyncMock,
|
||||
resolution_info: AsyncMock,
|
||||
) -> AsyncGenerator[None]:
|
||||
"""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": [],
|
||||
},
|
||||
},
|
||||
)
|
||||
aioclient_mock.get(
|
||||
"http://127.0.0.1/network/info",
|
||||
json={
|
||||
|
Loading…
x
Reference in New Issue
Block a user