Files

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1211 lines
36 KiB
Python
Raw Permalink Normal View History

"""Test issues from supervisor issues."""
from __future__ import annotations
from collections.abc import Generator
2024-03-16 02:10:24 +01:00
from datetime import timedelta
import os
from typing import Any
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,
)
2024-03-16 02:10:24 +01:00
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from .test_init import MOCK_ENVIRON
from tests.typing import WebSocketGenerator
@pytest.fixture(autouse=True)
async def setup_repairs(hass: HomeAssistant) -> None:
"""Set up the repairs integration."""
assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})
@pytest.fixture(autouse=True)
def fixture_supervisor_environ() -> Generator[None]:
"""Mock os environ for supervisor."""
with patch.dict(os.environ, MOCK_ENVIRON):
yield
def mock_resolution_info(
supervisor_client: AsyncMock,
unsupported: list[UnsupportedReason] | None = None,
unhealthy: list[UnhealthyReason] | None = None,
issues: list[Issue] | None = None,
suggestions_by_issue: dict[UUID, list[Suggestion]] | None = None,
suggestion_result: SupervisorError | None = None,
) -> None:
"""Mock resolution/info endpoint with unsupported/unhealthy reasons and/or issues."""
supervisor_client.resolution.info.return_value = ResolutionInfo(
unsupported=unsupported or [],
unhealthy=unhealthy or [],
issues=issues or [],
suggestions=[
suggestion
for issue_list in suggestions_by_issue.values()
for suggestion in issue_list
]
if suggestions_by_issue
else [],
checks=[
2026-03-16 08:11:48 -04:00
Check(enabled=True, slug=CheckType.DOCKER_CONFIG),
Check(enabled=True, slug=CheckType.FREE_SPACE),
],
)
if suggestions_by_issue:
async def mock_suggestions_for_issue(uuid: UUID) -> list[Suggestion]:
"""Mock of suggestions for issue api."""
return suggestions_by_issue.get(uuid, [])
supervisor_client.resolution.suggestions_for_issue.side_effect = (
mock_suggestions_for_issue
)
supervisor_client.resolution.apply_suggestion.side_effect = suggestion_result
def assert_repair_in_list(
issues: list[dict[str, Any]], unhealthy: bool, reason: str
) -> None:
"""Assert repair for unhealthy/unsupported in list."""
repair_type = "unhealthy" if unhealthy else "unsupported"
assert {
"breaks_in_ha_version": None,
"created": ANY,
"dismissed_version": None,
"domain": "hassio",
"ignored": False,
"is_fixable": False,
"issue_id": f"{repair_type}_system_{reason}",
"issue_domain": None,
"learn_more_url": f"https://www.home-assistant.io/more-info/{repair_type}/{reason}",
"severity": "critical" if unhealthy else "warning",
2022-11-01 21:29:11 -04:00
"translation_key": f"{repair_type}_{reason}",
"translation_placeholders": None,
} in issues
def assert_issue_repair_in_list(
issues: list[dict[str, Any]],
uuid: str,
context: str,
type_: str,
fixable: bool,
*,
reference: str | None = None,
placeholders: dict[str, str] | None = None,
) -> None:
"""Assert repair for unhealthy/unsupported in list."""
if reference:
placeholders = (placeholders or {}) | {"reference": reference}
assert {
"breaks_in_ha_version": None,
"created": ANY,
"dismissed_version": None,
"domain": "hassio",
"ignored": False,
"is_fixable": fixable,
"issue_id": uuid,
"issue_domain": None,
"learn_more_url": None,
"severity": "warning",
"translation_key": f"issue_{context}_{type_}",
"translation_placeholders": placeholders,
} in issues
@pytest.mark.usefixtures("all_setup_requests")
async def test_unhealthy_issues(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test issues added for unhealthy systems."""
mock_resolution_info(
supervisor_client, unhealthy=[UnhealthyReason.DOCKER, UnhealthyReason.SETUP]
)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="setup")
@pytest.mark.usefixtures("all_setup_requests")
@pytest.mark.parametrize("unhealthy_reason", list(UnhealthyReason))
async def test_unhealthy_reasons(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
unhealthy_reason: UnhealthyReason,
) -> None:
"""Test all unhealthy reasons in client library are properly made into repairs with a translation."""
mock_resolution_info(supervisor_client, unhealthy=[unhealthy_reason])
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_repair_in_list(
msg["result"]["issues"], unhealthy=True, reason=unhealthy_reason.value
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_unsupported_issues(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test issues added for unsupported systems."""
mock_resolution_info(
supervisor_client,
2026-03-16 08:11:48 -04:00
unsupported=[UnsupportedReason.CONNECTIVITY_CHECK, UnsupportedReason.OS],
)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert_repair_in_list(
2026-03-16 08:11:48 -04:00
msg["result"]["issues"], unhealthy=False, reason="connectivity_check"
)
assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")
@pytest.mark.usefixtures("all_setup_requests")
@pytest.mark.parametrize(
"unsupported_reason",
[r for r in UnsupportedReason if r != UnsupportedReason.PRIVILEGED],
)
async def test_unsupported_reasons(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
unsupported_reason: UnsupportedReason,
) -> None:
"""Test all unsupported reasons in client library are properly made into repairs with a translation."""
mock_resolution_info(supervisor_client, unsupported=[unsupported_reason])
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_repair_in_list(
msg["result"]["issues"], unhealthy=False, reason=unsupported_reason.value
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_unhealthy_issues_add_remove(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test unhealthy issues added and removed from dispatches."""
mock_resolution_info(supervisor_client)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "health_changed",
"data": {
"healthy": False,
"unhealthy_reasons": ["docker"],
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
await client.send_json(
{
"id": 3,
"type": "supervisor/event",
"data": {
"event": "health_changed",
"data": {"healthy": True},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 4, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {"issues": []}
@pytest.mark.usefixtures("all_setup_requests")
async def test_unsupported_issues_add_remove(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test unsupported issues added and removed from dispatches."""
mock_resolution_info(supervisor_client)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "supported_changed",
"data": {
"supported": False,
"unsupported_reasons": ["os"],
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")
await client.send_json(
{
"id": 3,
"type": "supervisor/event",
"data": {
"event": "supported_changed",
"data": {"supported": True},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 4, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {"issues": []}
@pytest.mark.usefixtures("all_setup_requests")
async def test_reset_issues_supervisor_restart(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""All issues reset on supervisor restart."""
mock_resolution_info(
supervisor_client,
unsupported=[UnsupportedReason.OS],
unhealthy=[UnhealthyReason.DOCKER],
issues=[
Issue(
type=IssueType.REBOOT_REQUIRED,
context=ContextType.SYSTEM,
reference=None,
uuid=(uuid := uuid4()),
)
],
suggestions_by_issue={
uuid: [
Suggestion(
SuggestionType.EXECUTE_REBOOT,
context=ContextType.SYSTEM,
reference=None,
uuid=uuid4(),
auto=False,
)
]
},
)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 3
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")
assert_issue_repair_in_list(
msg["result"]["issues"],
uuid=uuid.hex,
context="system",
type_="reboot_required",
fixable=True,
reference=None,
)
mock_resolution_info(supervisor_client)
await client.send_json(
{
"id": 2,
"type": "supervisor/event",
"data": {
"event": "supervisor_update",
"update_key": "supervisor",
"data": {"startup": "complete"},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 3, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {"issues": []}
@pytest.mark.usefixtures("all_setup_requests")
async def test_no_reset_issues_supervisor_update_found(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Issues do not reset because a supervisor update was found."""
mock_resolution_info(
supervisor_client,
unsupported=[UnsupportedReason.OS],
)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
mock_resolution_info(supervisor_client)
await client.send_json(
{
"id": 2,
"type": "supervisor/event",
"data": {
"event": "supervisor_update",
"update_key": "supervisor",
"data": {},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 3, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
@pytest.mark.usefixtures("all_setup_requests")
async def test_reasons_added_and_removed(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test an unsupported/unhealthy reasons being added and removed at same time."""
mock_resolution_info(
supervisor_client,
unsupported=[UnsupportedReason.OS],
unhealthy=[UnhealthyReason.DOCKER],
)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="docker")
assert_repair_in_list(msg["result"]["issues"], unhealthy=False, reason="os")
mock_resolution_info(
supervisor_client,
2026-03-16 08:11:48 -04:00
unsupported=[UnsupportedReason.CONNECTIVITY_CHECK],
unhealthy=[UnhealthyReason.SETUP],
)
await client.send_json(
{
"id": 2,
"type": "supervisor/event",
"data": {
"event": "supervisor_update",
"update_key": "supervisor",
"data": {"startup": "complete"},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 3, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="setup")
assert_repair_in_list(
2026-03-16 08:11:48 -04:00
msg["result"]["issues"], unhealthy=False, reason="connectivity_check"
)
2022-11-01 21:29:11 -04:00
@pytest.mark.usefixtures("all_setup_requests")
2022-11-01 21:29:11 -04:00
async def test_ignored_unsupported_skipped(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
2022-11-01 21:29:11 -04:00
"""Unsupported reasons which have an identical unhealthy reason are ignored."""
mock_resolution_info(
supervisor_client,
unsupported=[UnsupportedReason.PRIVILEGED],
unhealthy=[UnhealthyReason.PRIVILEGED],
2022-11-01 21:29:11 -04:00
)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_repair_in_list(msg["result"]["issues"], unhealthy=True, reason="privileged")
@pytest.mark.usefixtures("all_setup_requests")
2022-11-01 21:29:11 -04:00
async def test_new_unsupported_unhealthy_reason(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
2022-11-01 21:29:11 -04:00
"""New unsupported/unhealthy reasons result in a generic repair until next core update."""
mock_resolution_info(
supervisor_client,
unsupported=["fake_unsupported"],
unhealthy=["fake_unhealthy"],
2022-11-01 21:29:11 -04:00
)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert {
"breaks_in_ha_version": None,
"created": ANY,
"dismissed_version": None,
"domain": "hassio",
"ignored": False,
"is_fixable": False,
"issue_id": "unhealthy_system_fake_unhealthy",
"issue_domain": None,
"learn_more_url": "https://www.home-assistant.io/more-info/unhealthy/fake_unhealthy",
"severity": "critical",
"translation_key": "unhealthy",
"translation_placeholders": {"reason": "fake_unhealthy"},
} in msg["result"]["issues"]
assert {
"breaks_in_ha_version": None,
"created": ANY,
"dismissed_version": None,
"domain": "hassio",
"ignored": False,
"is_fixable": False,
"issue_id": "unsupported_system_fake_unsupported",
"issue_domain": None,
"learn_more_url": "https://www.home-assistant.io/more-info/unsupported/fake_unsupported",
"severity": "warning",
"translation_key": "unsupported",
"translation_placeholders": {"reason": "fake_unsupported"},
} in msg["result"]["issues"]
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test repairs added for supervisor issue."""
mock_resolution_info(
supervisor_client,
issues=[
Issue(
type=IssueType.DETACHED_ADDON_MISSING,
context=ContextType.ADDON,
reference="test",
uuid=(uuid_issue1 := uuid4()),
),
Issue(
type=IssueType.MULTIPLE_DATA_DISKS,
context=ContextType.SYSTEM,
reference="/dev/sda1",
uuid=(uuid_issue2 := uuid4()),
),
Issue(
type="should_not_be_repair",
context=ContextType.OS,
reference=None,
uuid=uuid4(),
),
],
suggestions_by_issue={
uuid_issue2: [
Suggestion(
type=SuggestionType.RENAME_DATA_DISK,
context=ContextType.SYSTEM,
reference="/dev/sda1",
uuid=uuid4(),
auto=False,
)
]
},
)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 2
assert_issue_repair_in_list(
msg["result"]["issues"],
uuid=uuid_issue1.hex,
context="addon",
type_="detached_addon_missing",
fixable=False,
reference="test",
placeholders={"addon_url": "/hassio/addon/test", "addon": "test"},
)
assert_issue_repair_in_list(
msg["result"]["issues"],
uuid=uuid_issue2.hex,
context="system",
type_="multiple_data_disks",
fixable=True,
reference="/dev/sda1",
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_initial_failure(
hass: HomeAssistant,
supervisor_client: AsyncMock,
resolution_info: AsyncMock,
hass_ws_client: WebSocketGenerator,
2024-03-16 02:10:24 +01:00
freezer: FrozenDateTimeFactory,
) -> None:
"""Test issues manager retries after initial update failure."""
mock_resolution_info(
supervisor_client,
unsupported=[],
unhealthy=[],
issues=[
Issue(
type=IssueType.REBOOT_REQUIRED,
context=ContextType.SYSTEM,
reference=None,
uuid=(uuid := uuid4()),
)
],
suggestions_by_issue={
uuid: [
Suggestion(
SuggestionType.EXECUTE_REBOOT,
context=ContextType.SYSTEM,
reference=None,
uuid=uuid4(),
auto=False,
)
]
},
)
resolution_info.side_effect = [
SupervisorBadRequestError("System is not ready with state: setup"),
resolution_info.return_value,
]
with patch("homeassistant.components.hassio.issues.REQUEST_REFRESH_DELAY", new=0.1):
result = await async_setup_component(hass, "hassio", {})
await hass.async_block_till_done()
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 0
2024-03-16 02:10:24 +01:00
freezer.tick(timedelta(milliseconds=200))
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_add_remove(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test supervisor issues added and removed from dispatches."""
mock_resolution_info(supervisor_client)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "issue_changed",
"data": {
"uuid": (issue_uuid := uuid4().hex),
"type": "reboot_required",
"context": "system",
"reference": None,
"suggestions": [
{
"uuid": uuid4().hex,
"type": "execute_reboot",
"context": "system",
"reference": None,
}
],
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_issue_repair_in_list(
msg["result"]["issues"],
uuid=issue_uuid,
context="system",
type_="reboot_required",
fixable=True,
reference=None,
)
await client.send_json(
{
"id": 3,
"type": "supervisor/event",
"data": {
"event": "issue_removed",
"data": {
"uuid": issue_uuid,
"type": "reboot_required",
"context": "system",
"reference": None,
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 4, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert msg["result"] == {"issues": []}
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_suggestions_fail(
hass: HomeAssistant,
supervisor_client: AsyncMock,
resolution_suggestions_for_issue: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test failing to get suggestions for issue skips it."""
mock_resolution_info(
supervisor_client,
issues=[
Issue(
type=IssueType.REBOOT_REQUIRED,
context=ContextType.SYSTEM,
reference=None,
uuid=uuid4(),
)
],
)
resolution_suggestions_for_issue.side_effect = SupervisorTimeoutError
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json({"id": 1, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 0
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_remove_missing_issue_without_error(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test HA skips message to remove issue that it didn't know about (sync issue)."""
mock_resolution_info(supervisor_client)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 5,
"type": "supervisor/event",
"data": {
"event": "issue_removed",
"data": {
"uuid": "1234",
"type": "reboot_required",
"context": "system",
"reference": None,
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
@pytest.mark.usefixtures("all_setup_requests")
async def test_system_is_not_ready(
hass: HomeAssistant,
resolution_info: AsyncMock,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Ensure hassio starts despite error."""
resolution_info.side_effect = SupervisorBadRequestError(
"System is not ready with state: setup"
)
assert await async_setup_component(hass, "hassio", {})
assert "Failed to update supervisor issues" in caplog.text
@pytest.mark.parametrize(
"all_setup_requests", [{"include_addons": True}], indirect=True
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_detached_addon_missing(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test supervisor issue for detached addon due to missing repository."""
mock_resolution_info(supervisor_client)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "issue_changed",
"data": {
"uuid": (issue_uuid := uuid4().hex),
"type": "detached_addon_missing",
"context": "addon",
"reference": "test",
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_issue_repair_in_list(
msg["result"]["issues"],
uuid=issue_uuid,
context="addon",
type_="detached_addon_missing",
fixable=False,
placeholders={
"reference": "test",
"addon": "test",
"addon_url": "/hassio/addon/test",
},
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_ntp_sync_failed(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test supervisor issue for NTP sync failed."""
mock_resolution_info(supervisor_client)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "issue_changed",
"data": {
"uuid": (issue_uuid := uuid4().hex),
"type": "ntp_sync_failed",
"context": "system",
"reference": None,
"suggestions": [
{
"uuid": uuid4().hex,
"type": "enable_ntp",
"context": "system",
"reference": None,
}
],
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_issue_repair_in_list(
msg["result"]["issues"],
uuid=issue_uuid,
context="system",
type_="ntp_sync_failed",
fixable=True,
placeholders=None,
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_disk_lifetime(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test supervisor issue for disk lifetime nearly exceeded."""
mock_resolution_info(supervisor_client)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "issue_changed",
"data": {
"uuid": (issue_uuid := uuid4().hex),
"type": "disk_lifetime",
"context": "system",
"reference": None,
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_issue_repair_in_list(
msg["result"]["issues"],
uuid=issue_uuid,
context="system",
type_="disk_lifetime",
fixable=False,
placeholders=None,
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_free_space(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test supervisor issue for too little free space remaining."""
mock_resolution_info(supervisor_client)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "issue_changed",
"data": {
"uuid": (issue_uuid := uuid4().hex),
"type": "free_space",
"context": "system",
"reference": None,
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_issue_repair_in_list(
msg["result"]["issues"],
uuid=issue_uuid,
context="system",
type_="free_space",
fixable=False,
placeholders={
"more_info_free_space": "https://www.home-assistant.io/more-info/free-space",
"storage_url": "/config/storage",
"free_space": "1.6",
},
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_free_space_host_info_fail(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
host_info: AsyncMock,
) -> None:
"""Test supervisor issue for too little free space remaining without host info."""
mock_resolution_info(supervisor_client)
host_info.side_effect = SupervisorError()
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "issue_changed",
"data": {
"uuid": (issue_uuid := uuid4().hex),
"type": "free_space",
"context": "system",
"reference": None,
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_issue_repair_in_list(
msg["result"]["issues"],
uuid=issue_uuid,
context="system",
type_="free_space",
fixable=False,
placeholders={
"more_info_free_space": "https://www.home-assistant.io/more-info/free-space",
"storage_url": "/config/storage",
"free_space": "<2",
},
)
@pytest.mark.parametrize(
"all_setup_requests", [{"include_addons": True}], indirect=True
)
@pytest.mark.usefixtures("all_setup_requests")
async def test_supervisor_issues_addon_pwned(
hass: HomeAssistant,
supervisor_client: AsyncMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test supervisor issue for pwned secret in an addon."""
mock_resolution_info(supervisor_client)
result = await async_setup_component(hass, "hassio", {})
assert result
client = await hass_ws_client(hass)
await client.send_json(
{
"id": 1,
"type": "supervisor/event",
"data": {
"event": "issue_changed",
"data": {
"uuid": (issue_uuid := uuid4().hex),
"type": "pwned",
"context": "addon",
"reference": "test",
},
},
}
)
msg = await client.receive_json()
assert msg["success"]
await hass.async_block_till_done()
await client.send_json({"id": 2, "type": "repairs/list_issues"})
msg = await client.receive_json()
assert msg["success"]
assert len(msg["result"]["issues"]) == 1
assert_issue_repair_in_list(
msg["result"]["issues"],
uuid=issue_uuid,
context="addon",
type_="pwned",
fixable=False,
placeholders={
"reference": "test",
"addon": "test",
"addon_url": "/hassio/addon/test",
"more_info_pwned": "https://www.home-assistant.io/more-info/pwned-passwords",
},
)