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