mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 01:07:10 +00:00
Teach resolution center about fixing issues (#74694)
This commit is contained in:
parent
cf612c4bec
commit
b0fde206b8
@ -4,16 +4,19 @@ from __future__ import annotations
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import websocket_api
|
||||
from . import issue_handler, websocket_api
|
||||
from .const import DOMAIN
|
||||
from .issue_handler import async_create_issue, async_delete_issue
|
||||
from .issue_handler import ResolutionCenterFlow, async_create_issue, async_delete_issue
|
||||
from .issue_registry import async_load as async_load_issue_registry
|
||||
|
||||
__all__ = ["DOMAIN", "async_create_issue", "async_delete_issue"]
|
||||
__all__ = ["DOMAIN", "ResolutionCenterFlow", "async_create_issue", "async_delete_issue"]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up Resolution Center."""
|
||||
hass.data[DOMAIN] = {}
|
||||
|
||||
issue_handler.async_setup(hass)
|
||||
websocket_api.async_setup(hass)
|
||||
await async_load_issue_registry(hass)
|
||||
|
||||
|
@ -1,12 +1,85 @@
|
||||
"""The resolution center integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from awesomeversion import AwesomeVersion, AwesomeVersionStrategy
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.integration_platform import (
|
||||
async_process_integration_platforms,
|
||||
)
|
||||
|
||||
from .const import DOMAIN
|
||||
from .issue_registry import async_get as async_get_issue_registry
|
||||
from .models import IssueSeverity
|
||||
from .models import IssueSeverity, ResolutionCenterFlow, ResolutionCenterProtocol
|
||||
|
||||
|
||||
class ResolutionCenterFlowManager(data_entry_flow.FlowManager):
|
||||
"""Manage resolution center flows."""
|
||||
|
||||
async def async_create_flow(
|
||||
self,
|
||||
handler_key: Any,
|
||||
*,
|
||||
context: dict[str, Any] | None = None,
|
||||
data: dict[str, Any] | None = None,
|
||||
) -> ResolutionCenterFlow:
|
||||
"""Create a flow. platform is a resolution center module."""
|
||||
if "platforms" not in self.hass.data[DOMAIN]:
|
||||
await async_process_resolution_center_platforms(self.hass)
|
||||
|
||||
platforms: dict[str, ResolutionCenterProtocol] = self.hass.data[DOMAIN][
|
||||
"platforms"
|
||||
]
|
||||
if handler_key not in platforms:
|
||||
raise data_entry_flow.UnknownHandler
|
||||
platform = platforms[handler_key]
|
||||
|
||||
assert data and "issue_id" in data
|
||||
issue_id = data["issue_id"]
|
||||
|
||||
issue_registry = async_get_issue_registry(self.hass)
|
||||
issue = issue_registry.async_get_issue(handler_key, issue_id)
|
||||
if issue is None or not issue.is_fixable:
|
||||
raise data_entry_flow.UnknownStep
|
||||
|
||||
return await platform.async_create_fix_flow(self.hass, issue_id)
|
||||
|
||||
async def async_finish_flow(
|
||||
self, flow: data_entry_flow.FlowHandler, result: data_entry_flow.FlowResult
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Complete a fix flow."""
|
||||
async_delete_issue(self.hass, flow.handler, flow.init_data["issue_id"])
|
||||
if "result" not in result:
|
||||
result["result"] = None
|
||||
return result
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup(hass: HomeAssistant) -> None:
|
||||
"""Initialize resolution center."""
|
||||
hass.data[DOMAIN]["flow_manager"] = ResolutionCenterFlowManager(hass)
|
||||
|
||||
|
||||
async def async_process_resolution_center_platforms(hass: HomeAssistant) -> None:
|
||||
"""Start processing resolution center platforms."""
|
||||
hass.data[DOMAIN]["platforms"] = {}
|
||||
|
||||
await async_process_integration_platforms(
|
||||
hass, DOMAIN, _register_resolution_center_platform
|
||||
)
|
||||
|
||||
|
||||
async def _register_resolution_center_platform(
|
||||
hass: HomeAssistant, integration_domain: str, platform: ResolutionCenterProtocol
|
||||
) -> None:
|
||||
"""Register a resolution center platform."""
|
||||
if not hasattr(platform, "async_create_fix_flow"):
|
||||
raise HomeAssistantError(f"Invalid resolution center platform {platform}")
|
||||
hass.data[DOMAIN]["platforms"][integration_domain] = platform
|
||||
|
||||
|
||||
@callback
|
||||
@ -16,6 +89,7 @@ def async_create_issue(
|
||||
issue_id: str,
|
||||
*,
|
||||
breaks_in_ha_version: str | None = None,
|
||||
is_fixable: bool,
|
||||
learn_more_url: str | None = None,
|
||||
severity: IssueSeverity,
|
||||
translation_key: str,
|
||||
@ -35,6 +109,7 @@ def async_create_issue(
|
||||
domain,
|
||||
issue_id,
|
||||
breaks_in_ha_version=breaks_in_ha_version,
|
||||
is_fixable=is_fixable,
|
||||
learn_more_url=learn_more_url,
|
||||
severity=severity,
|
||||
translation_key=translation_key,
|
||||
|
@ -25,6 +25,7 @@ class IssueEntry:
|
||||
breaks_in_ha_version: str | None
|
||||
dismissed_version: str | None
|
||||
domain: str
|
||||
is_fixable: bool | None
|
||||
issue_id: str
|
||||
learn_more_url: str | None
|
||||
severity: IssueSeverity | None
|
||||
@ -55,6 +56,7 @@ class IssueRegistry:
|
||||
issue_id: str,
|
||||
*,
|
||||
breaks_in_ha_version: str | None = None,
|
||||
is_fixable: bool,
|
||||
learn_more_url: str | None = None,
|
||||
severity: IssueSeverity,
|
||||
translation_key: str,
|
||||
@ -68,6 +70,7 @@ class IssueRegistry:
|
||||
breaks_in_ha_version=breaks_in_ha_version,
|
||||
dismissed_version=None,
|
||||
domain=domain,
|
||||
is_fixable=is_fixable,
|
||||
issue_id=issue_id,
|
||||
learn_more_url=learn_more_url,
|
||||
severity=severity,
|
||||
@ -81,6 +84,7 @@ class IssueRegistry:
|
||||
issue,
|
||||
active=True,
|
||||
breaks_in_ha_version=breaks_in_ha_version,
|
||||
is_fixable=is_fixable,
|
||||
learn_more_url=learn_more_url,
|
||||
severity=severity,
|
||||
translation_key=translation_key,
|
||||
@ -127,6 +131,7 @@ class IssueRegistry:
|
||||
breaks_in_ha_version=None,
|
||||
dismissed_version=issue["dismissed_version"],
|
||||
domain=issue["domain"],
|
||||
is_fixable=None,
|
||||
issue_id=issue["issue_id"],
|
||||
learn_more_url=None,
|
||||
severity=None,
|
||||
|
@ -3,5 +3,6 @@
|
||||
"name": "Resolution Center",
|
||||
"config_flow": false,
|
||||
"documentation": "https://www.home-assistant.io/integrations/resolution_center",
|
||||
"codeowners": ["@home-assistant/core"]
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"dependencies": ["http"]
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
"""Models for Resolution Center."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
class IssueSeverity(StrEnum):
|
||||
@ -10,3 +14,16 @@ class IssueSeverity(StrEnum):
|
||||
CRITICAL = "critical"
|
||||
ERROR = "error"
|
||||
WARNING = "warning"
|
||||
|
||||
|
||||
class ResolutionCenterFlow(data_entry_flow.FlowHandler):
|
||||
"""Handle a flow for fixing an issue."""
|
||||
|
||||
|
||||
class ResolutionCenterProtocol(Protocol):
|
||||
"""Define the format of resolution center platforms."""
|
||||
|
||||
async def async_create_fix_flow(
|
||||
self, hass: HomeAssistant, issue_id: str
|
||||
) -> ResolutionCenterFlow:
|
||||
"""Create a flow to fix a fixable issue."""
|
||||
|
@ -2,13 +2,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.auth.permissions.const import POLICY_EDIT
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import Unauthorized
|
||||
from homeassistant.helpers.data_entry_flow import (
|
||||
FlowManagerIndexView,
|
||||
FlowManagerResourceView,
|
||||
)
|
||||
|
||||
from .const import DOMAIN
|
||||
from .issue_handler import async_dismiss_issue
|
||||
from .issue_registry import async_get as async_get_issue_registry
|
||||
|
||||
@ -19,6 +30,13 @@ def async_setup(hass: HomeAssistant) -> None:
|
||||
websocket_api.async_register_command(hass, ws_dismiss_issue)
|
||||
websocket_api.async_register_command(hass, ws_list_issues)
|
||||
|
||||
hass.http.register_view(
|
||||
ResolutionCenterFlowIndexView(hass.data[DOMAIN]["flow_manager"])
|
||||
)
|
||||
hass.http.register_view(
|
||||
ResolutionCenterFlowResourceView(hass.data[DOMAIN]["flow_manager"])
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
@websocket_api.websocket_command(
|
||||
@ -60,3 +78,63 @@ def ws_list_issues(
|
||||
]
|
||||
|
||||
connection.send_result(msg["id"], {"issues": issues})
|
||||
|
||||
|
||||
class ResolutionCenterFlowIndexView(FlowManagerIndexView):
|
||||
"""View to create issue fix flows."""
|
||||
|
||||
url = "/api/resolution_center/issues/fix"
|
||||
name = "api:resolution_center:issues:fix"
|
||||
|
||||
@RequestDataValidator(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required("handler"): str,
|
||||
vol.Required("issue_id"): str,
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
)
|
||||
async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response:
|
||||
"""Handle a POST request."""
|
||||
if not request["hass_user"].is_admin:
|
||||
raise Unauthorized(permission=POLICY_EDIT)
|
||||
|
||||
try:
|
||||
result = await self._flow_mgr.async_init(
|
||||
data["handler"],
|
||||
data={"issue_id": data["issue_id"]},
|
||||
)
|
||||
except data_entry_flow.UnknownHandler:
|
||||
return self.json_message("Invalid handler specified", HTTPStatus.NOT_FOUND)
|
||||
except data_entry_flow.UnknownStep:
|
||||
return self.json_message(
|
||||
"Handler does not support user", HTTPStatus.BAD_REQUEST
|
||||
)
|
||||
|
||||
result = self._prepare_result_json(result)
|
||||
|
||||
return self.json(result) # pylint: disable=arguments-differ
|
||||
|
||||
|
||||
class ResolutionCenterFlowResourceView(FlowManagerResourceView):
|
||||
"""View to interact with the option flow manager."""
|
||||
|
||||
url = "/api/resolution_center/issues/fix/{flow_id}"
|
||||
name = "api:resolution_center:issues:fix:resource"
|
||||
|
||||
async def get(self, request: web.Request, flow_id: str) -> web.Response:
|
||||
"""Get the current state of a data_entry_flow."""
|
||||
if not request["hass_user"].is_admin:
|
||||
raise Unauthorized(permission=POLICY_EDIT)
|
||||
|
||||
return await super().get(request, flow_id)
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
async def post(self, request: web.Request, flow_id: str) -> web.Response:
|
||||
"""Handle a POST request."""
|
||||
if not request["hass_user"].is_admin:
|
||||
raise Unauthorized(permission=POLICY_EDIT)
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
return await super().post(request, flow_id) # type: ignore[no-any-return]
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""Test the resolution center websocket API."""
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.resolution_center import (
|
||||
@ -6,11 +8,16 @@ from homeassistant.components.resolution_center import (
|
||||
async_delete_issue,
|
||||
)
|
||||
from homeassistant.components.resolution_center.const import DOMAIN
|
||||
from homeassistant.components.resolution_center.issue_handler import async_dismiss_issue
|
||||
from homeassistant.components.resolution_center.issue_handler import (
|
||||
async_dismiss_issue,
|
||||
async_process_resolution_center_platforms,
|
||||
)
|
||||
from homeassistant.const import __version__ as ha_version
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import mock_platform
|
||||
|
||||
|
||||
async def test_create_update_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
"""Test creating and updating issues."""
|
||||
@ -29,6 +36,7 @@ async def test_create_update_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
"breaks_in_ha_version": "2022.9.0dev0",
|
||||
"domain": "test",
|
||||
"issue_id": "issue_1",
|
||||
"is_fixable": True,
|
||||
"learn_more_url": "https://theuselessweb.com",
|
||||
"severity": "error",
|
||||
"translation_key": "abc_123",
|
||||
@ -38,6 +46,7 @@ async def test_create_update_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
"breaks_in_ha_version": "2022.8",
|
||||
"domain": "test",
|
||||
"issue_id": "issue_2",
|
||||
"is_fixable": False,
|
||||
"learn_more_url": "https://theuselessweb.com/abc",
|
||||
"severity": "other",
|
||||
"translation_key": "even_worse",
|
||||
@ -51,6 +60,7 @@ async def test_create_update_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
issue["domain"],
|
||||
issue["issue_id"],
|
||||
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
||||
is_fixable=issue["is_fixable"],
|
||||
learn_more_url=issue["learn_more_url"],
|
||||
severity=issue["severity"],
|
||||
translation_key=issue["translation_key"],
|
||||
@ -78,6 +88,7 @@ async def test_create_update_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
issues[0]["domain"],
|
||||
issues[0]["issue_id"],
|
||||
breaks_in_ha_version=issues[0]["breaks_in_ha_version"],
|
||||
is_fixable=issues[0]["is_fixable"],
|
||||
learn_more_url="blablabla",
|
||||
severity=issues[0]["severity"],
|
||||
translation_key=issues[0]["translation_key"],
|
||||
@ -109,6 +120,7 @@ async def test_create_issue_invalid_version(
|
||||
"breaks_in_ha_version": ha_version,
|
||||
"domain": "test",
|
||||
"issue_id": "issue_1",
|
||||
"is_fixable": True,
|
||||
"learn_more_url": "https://theuselessweb.com",
|
||||
"severity": "error",
|
||||
"translation_key": "abc_123",
|
||||
@ -121,6 +133,7 @@ async def test_create_issue_invalid_version(
|
||||
issue["domain"],
|
||||
issue["issue_id"],
|
||||
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
||||
is_fixable=issue["is_fixable"],
|
||||
learn_more_url=issue["learn_more_url"],
|
||||
severity=issue["severity"],
|
||||
translation_key=issue["translation_key"],
|
||||
@ -150,6 +163,7 @@ async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
{
|
||||
"breaks_in_ha_version": "2022.9",
|
||||
"domain": "test",
|
||||
"is_fixable": True,
|
||||
"issue_id": "issue_1",
|
||||
"learn_more_url": "https://theuselessweb.com",
|
||||
"severity": "error",
|
||||
@ -164,6 +178,7 @@ async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
issue["domain"],
|
||||
issue["issue_id"],
|
||||
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
||||
is_fixable=issue["is_fixable"],
|
||||
learn_more_url=issue["learn_more_url"],
|
||||
severity=issue["severity"],
|
||||
translation_key=issue["translation_key"],
|
||||
@ -246,6 +261,7 @@ async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
issues[0]["domain"],
|
||||
issues[0]["issue_id"],
|
||||
breaks_in_ha_version=issues[0]["breaks_in_ha_version"],
|
||||
is_fixable=issues[0]["is_fixable"],
|
||||
learn_more_url="blablabla",
|
||||
severity=issues[0]["severity"],
|
||||
translation_key=issues[0]["translation_key"],
|
||||
@ -275,6 +291,7 @@ async def test_delete_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
"breaks_in_ha_version": "2022.9",
|
||||
"domain": "fake_integration",
|
||||
"issue_id": "issue_1",
|
||||
"is_fixable": True,
|
||||
"learn_more_url": "https://theuselessweb.com",
|
||||
"severity": "error",
|
||||
"translation_key": "abc_123",
|
||||
@ -288,6 +305,7 @@ async def test_delete_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
issue["domain"],
|
||||
issue["issue_id"],
|
||||
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
||||
is_fixable=issue["is_fixable"],
|
||||
learn_more_url=issue["learn_more_url"],
|
||||
severity=issue["severity"],
|
||||
translation_key=issue["translation_key"],
|
||||
@ -344,3 +362,25 @@ async def test_delete_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
|
||||
assert msg["success"]
|
||||
assert msg["result"] == {"issues": []}
|
||||
|
||||
|
||||
async def test_non_compliant_platform(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
"""Test non-compliant platforms are not registered."""
|
||||
|
||||
hass.config.components.add("fake_integration")
|
||||
hass.config.components.add("integration_without_diagnostics")
|
||||
mock_platform(
|
||||
hass,
|
||||
"fake_integration.resolution_center",
|
||||
Mock(async_create_fix_flow=AsyncMock(return_value=True)),
|
||||
)
|
||||
mock_platform(
|
||||
hass,
|
||||
"integration_without_diagnostics.resolution_center",
|
||||
Mock(spec=[]),
|
||||
)
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
await async_process_resolution_center_platforms(hass)
|
||||
|
||||
assert list(hass.data[DOMAIN]["platforms"].keys()) == ["fake_integration"]
|
||||
|
@ -20,6 +20,7 @@ async def test_load_issues(hass: HomeAssistant) -> None:
|
||||
"breaks_in_ha_version": "2022.9",
|
||||
"domain": "test",
|
||||
"issue_id": "issue_1",
|
||||
"is_fixable": True,
|
||||
"learn_more_url": "https://theuselessweb.com",
|
||||
"severity": "error",
|
||||
"translation_key": "abc_123",
|
||||
@ -29,6 +30,7 @@ async def test_load_issues(hass: HomeAssistant) -> None:
|
||||
"breaks_in_ha_version": "2022.8",
|
||||
"domain": "test",
|
||||
"issue_id": "issue_2",
|
||||
"is_fixable": True,
|
||||
"learn_more_url": "https://theuselessweb.com/abc",
|
||||
"severity": "other",
|
||||
"translation_key": "even_worse",
|
||||
@ -42,6 +44,7 @@ async def test_load_issues(hass: HomeAssistant) -> None:
|
||||
issue["domain"],
|
||||
issue["issue_id"],
|
||||
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
||||
is_fixable=issue["is_fixable"],
|
||||
learn_more_url=issue["learn_more_url"],
|
||||
severity=issue["severity"],
|
||||
translation_key=issue["translation_key"],
|
||||
|
@ -1,22 +1,33 @@
|
||||
"""Test the resolution center websocket API."""
|
||||
from homeassistant.components.resolution_center import async_create_issue
|
||||
from __future__ import annotations
|
||||
|
||||
from http import HTTPStatus
|
||||
from unittest.mock import ANY, AsyncMock, Mock
|
||||
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.resolution_center import (
|
||||
ResolutionCenterFlow,
|
||||
async_create_issue,
|
||||
)
|
||||
from homeassistant.components.resolution_center.const import DOMAIN
|
||||
from homeassistant.const import __version__ as ha_version
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import mock_platform
|
||||
|
||||
async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
"""Test we can dismiss an issue."""
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
async def create_issues(hass, ws_client):
|
||||
"""Create issues."""
|
||||
issues = [
|
||||
{
|
||||
"breaks_in_ha_version": "2022.9",
|
||||
"domain": "test",
|
||||
"domain": "fake_integration",
|
||||
"issue_id": "issue_1",
|
||||
"is_fixable": True,
|
||||
"learn_more_url": "https://theuselessweb.com",
|
||||
"severity": "error",
|
||||
"translation_key": "abc_123",
|
||||
@ -30,14 +41,15 @@ async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
issue["domain"],
|
||||
issue["issue_id"],
|
||||
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
||||
is_fixable=issue["is_fixable"],
|
||||
learn_more_url=issue["learn_more_url"],
|
||||
severity=issue["severity"],
|
||||
translation_key=issue["translation_key"],
|
||||
translation_placeholders=issue["translation_placeholders"],
|
||||
)
|
||||
|
||||
await client.send_json({"id": 1, "type": "resolution_center/list_issues"})
|
||||
msg = await client.receive_json()
|
||||
await ws_client.send_json({"id": 1, "type": "resolution_center/list_issues"})
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert msg["success"]
|
||||
assert msg["result"] == {
|
||||
@ -51,11 +63,63 @@ async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
]
|
||||
}
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
class MockFixFlow(ResolutionCenterFlow):
|
||||
"""Handler for an issue fixing flow."""
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the first step of a fix flow."""
|
||||
|
||||
return await (self.async_step_confirm())
|
||||
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the confirm step of a fix flow."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title=None, data=None)
|
||||
|
||||
return self.async_show_form(step_id="confirm", data_schema=vol.Schema({}))
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def mock_resolution_center_integration(hass):
|
||||
"""Mock a resolution_center integration."""
|
||||
hass.config.components.add("fake_integration")
|
||||
hass.config.components.add("integration_without_diagnostics")
|
||||
|
||||
def async_create_fix_flow(hass, issue_id):
|
||||
return MockFixFlow()
|
||||
|
||||
mock_platform(
|
||||
hass,
|
||||
"fake_integration.resolution_center",
|
||||
Mock(async_create_fix_flow=AsyncMock(wraps=async_create_fix_flow)),
|
||||
)
|
||||
mock_platform(
|
||||
hass,
|
||||
"integration_without_diagnostics.resolution_center",
|
||||
Mock(spec=[]),
|
||||
)
|
||||
|
||||
|
||||
async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
"""Test we can dismiss an issue."""
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
issues = await create_issues(hass, client)
|
||||
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 2,
|
||||
"type": "resolution_center/dismiss_issue",
|
||||
"domain": "test",
|
||||
"domain": "fake_integration",
|
||||
"issue_id": "no_such_issue",
|
||||
}
|
||||
)
|
||||
@ -66,7 +130,7 @@ async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
{
|
||||
"id": 3,
|
||||
"type": "resolution_center/dismiss_issue",
|
||||
"domain": "test",
|
||||
"domain": "fake_integration",
|
||||
"issue_id": "issue_1",
|
||||
}
|
||||
)
|
||||
@ -90,6 +154,185 @@ async def test_dismiss_issue(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
}
|
||||
|
||||
|
||||
async def test_fix_non_existing_issue(
|
||||
hass: HomeAssistant, hass_client, hass_ws_client
|
||||
) -> None:
|
||||
"""Test trying to fix an issue that doesn't exist."""
|
||||
assert await async_setup_component(hass, "http", {})
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
client = await hass_client()
|
||||
|
||||
issues = await create_issues(hass, ws_client)
|
||||
|
||||
url = "/api/resolution_center/issues/fix"
|
||||
resp = await client.post(
|
||||
url, json={"handler": "no_such_integration", "issue_id": "no_such_issue"}
|
||||
)
|
||||
|
||||
assert resp.status != HTTPStatus.OK
|
||||
|
||||
url = "/api/resolution_center/issues/fix"
|
||||
resp = await client.post(
|
||||
url, json={"handler": "fake_integration", "issue_id": "no_such_issue"}
|
||||
)
|
||||
|
||||
assert resp.status != HTTPStatus.OK
|
||||
|
||||
await ws_client.send_json({"id": 3, "type": "resolution_center/list_issues"})
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert msg["success"]
|
||||
assert msg["result"] == {
|
||||
"issues": [
|
||||
dict(
|
||||
issue,
|
||||
dismissed=False,
|
||||
dismissed_version=None,
|
||||
)
|
||||
for issue in issues
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
async def test_fix_issue(hass: HomeAssistant, hass_client, hass_ws_client) -> None:
|
||||
"""Test we can fix an issue."""
|
||||
assert await async_setup_component(hass, "http", {})
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
client = await hass_client()
|
||||
|
||||
await create_issues(hass, ws_client)
|
||||
|
||||
url = "/api/resolution_center/issues/fix"
|
||||
resp = await client.post(
|
||||
url, json={"handler": "fake_integration", "issue_id": "issue_1"}
|
||||
)
|
||||
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data == {
|
||||
"data_schema": [],
|
||||
"description_placeholders": None,
|
||||
"errors": None,
|
||||
"flow_id": ANY,
|
||||
"handler": "fake_integration",
|
||||
"last_step": None,
|
||||
"step_id": "confirm",
|
||||
"type": "form",
|
||||
}
|
||||
|
||||
url = f"/api/resolution_center/issues/fix/{flow_id}"
|
||||
# Test we can get the status of the flow
|
||||
resp2 = await client.get(url)
|
||||
|
||||
assert resp2.status == HTTPStatus.OK
|
||||
data2 = await resp2.json()
|
||||
|
||||
assert data == data2
|
||||
|
||||
resp = await client.post(url)
|
||||
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
|
||||
flow_id = data["flow_id"]
|
||||
assert data == {
|
||||
"description": None,
|
||||
"description_placeholders": None,
|
||||
"flow_id": flow_id,
|
||||
"handler": "fake_integration",
|
||||
"title": None,
|
||||
"type": "create_entry",
|
||||
"version": 1,
|
||||
}
|
||||
|
||||
await ws_client.send_json({"id": 4, "type": "resolution_center/list_issues"})
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert msg["success"]
|
||||
assert msg["result"] == {"issues": []}
|
||||
|
||||
|
||||
async def test_fix_issue_unauth(
|
||||
hass: HomeAssistant, hass_client, hass_admin_user
|
||||
) -> None:
|
||||
"""Test we can't query the result if not authorized."""
|
||||
assert await async_setup_component(hass, "http", {})
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
hass_admin_user.groups = []
|
||||
|
||||
client = await hass_client()
|
||||
|
||||
url = "/api/resolution_center/issues/fix"
|
||||
resp = await client.post(
|
||||
url, json={"handler": "fake_integration", "issue_id": "issue_1"}
|
||||
)
|
||||
|
||||
assert resp.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
|
||||
async def test_get_progress_unauth(
|
||||
hass: HomeAssistant, hass_client, hass_ws_client, hass_admin_user
|
||||
) -> None:
|
||||
"""Test we can't fix an issue if not authorized."""
|
||||
assert await async_setup_component(hass, "http", {})
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
client = await hass_client()
|
||||
|
||||
await create_issues(hass, ws_client)
|
||||
|
||||
url = "/api/resolution_center/issues/fix"
|
||||
resp = await client.post(
|
||||
url, json={"handler": "fake_integration", "issue_id": "issue_1"}
|
||||
)
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
flow_id = data["flow_id"]
|
||||
|
||||
hass_admin_user.groups = []
|
||||
|
||||
url = f"/api/resolution_center/issues/fix/{flow_id}"
|
||||
# Test we can't get the status of the flow
|
||||
resp = await client.get(url)
|
||||
assert resp.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
|
||||
async def test_step_unauth(
|
||||
hass: HomeAssistant, hass_client, hass_ws_client, hass_admin_user
|
||||
) -> None:
|
||||
"""Test we can't fix an issue if not authorized."""
|
||||
assert await async_setup_component(hass, "http", {})
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
client = await hass_client()
|
||||
|
||||
await create_issues(hass, ws_client)
|
||||
|
||||
url = "/api/resolution_center/issues/fix"
|
||||
resp = await client.post(
|
||||
url, json={"handler": "fake_integration", "issue_id": "issue_1"}
|
||||
)
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
flow_id = data["flow_id"]
|
||||
|
||||
hass_admin_user.groups = []
|
||||
|
||||
url = f"/api/resolution_center/issues/fix/{flow_id}"
|
||||
# Test we can't get the status of the flow
|
||||
resp = await client.post(url)
|
||||
assert resp.status == HTTPStatus.UNAUTHORIZED
|
||||
|
||||
|
||||
async def test_list_issues(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
"""Test we can list issues."""
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
@ -106,6 +349,7 @@ async def test_list_issues(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
{
|
||||
"breaks_in_ha_version": "2022.9",
|
||||
"domain": "test",
|
||||
"is_fixable": True,
|
||||
"issue_id": "issue_1",
|
||||
"learn_more_url": "https://theuselessweb.com",
|
||||
"severity": "error",
|
||||
@ -115,6 +359,7 @@ async def test_list_issues(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
{
|
||||
"breaks_in_ha_version": "2022.8",
|
||||
"domain": "test",
|
||||
"is_fixable": False,
|
||||
"issue_id": "issue_2",
|
||||
"learn_more_url": "https://theuselessweb.com/abc",
|
||||
"severity": "other",
|
||||
@ -129,6 +374,7 @@ async def test_list_issues(hass: HomeAssistant, hass_ws_client) -> None:
|
||||
issue["domain"],
|
||||
issue["issue_id"],
|
||||
breaks_in_ha_version=issue["breaks_in_ha_version"],
|
||||
is_fixable=issue["is_fixable"],
|
||||
learn_more_url=issue["learn_more_url"],
|
||||
severity=issue["severity"],
|
||||
translation_key=issue["translation_key"],
|
||||
|
Loading…
x
Reference in New Issue
Block a user