From b3660901757d3a953cda3a7d70175ac5cb5d9cf8 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 5 Aug 2022 12:07:51 +0200 Subject: [PATCH] Allow creating fixable repairs issues without flows (#76224) * Allow creating fixable repairs issues without flows * Add test * Adjust test --- homeassistant/components/demo/__init__.py | 10 +++ homeassistant/components/demo/repairs.py | 9 ++- homeassistant/components/demo/strings.json | 13 +++- .../components/demo/translations/en.json | 13 +++- homeassistant/components/repairs/__init__.py | 2 + .../components/repairs/issue_handler.py | 37 +++++++--- tests/components/demo/test_init.py | 30 +++++++- .../components/repairs/test_websocket_api.py | 68 +++++++++++-------- 8 files changed, 140 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index 3f0bb09cdbd..2e01e2c3c6b 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -211,6 +211,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: translation_key="unfixable_problem", ) + async_create_issue( + hass, + DOMAIN, + "bad_psu", + is_fixable=True, + learn_more_url="https://www.youtube.com/watch?v=b9rntRxLlbU", + severity=IssueSeverity.CRITICAL, + translation_key="bad_psu", + ) + return True diff --git a/homeassistant/components/demo/repairs.py b/homeassistant/components/demo/repairs.py index e5d31c18971..2d7c8b4cbcc 100644 --- a/homeassistant/components/demo/repairs.py +++ b/homeassistant/components/demo/repairs.py @@ -5,7 +5,7 @@ from __future__ import annotations import voluptuous as vol from homeassistant import data_entry_flow -from homeassistant.components.repairs import RepairsFlow +from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow class DemoFixFlow(RepairsFlow): @@ -23,11 +23,16 @@ class DemoFixFlow(RepairsFlow): ) -> data_entry_flow.FlowResult: """Handle the confirm step of a fix flow.""" if user_input is not None: - return self.async_create_entry(title="Fixed issue", data={}) + return self.async_create_entry(title="", data={}) return self.async_show_form(step_id="confirm", data_schema=vol.Schema({})) async def async_create_fix_flow(hass, issue_id): """Create flow.""" + if issue_id == "bad_psu": + # The bad_psu issue doesn't have its own flow + return ConfirmRepairFlow() + + # Other issues have a custom flow return DemoFixFlow() diff --git a/homeassistant/components/demo/strings.json b/homeassistant/components/demo/strings.json index a3a5b11f336..e02d64f157f 100644 --- a/homeassistant/components/demo/strings.json +++ b/homeassistant/components/demo/strings.json @@ -1,13 +1,24 @@ { "title": "Demo", "issues": { + "bad_psu": { + "title": "The power supply is not stable", + "fix_flow": { + "step": { + "confirm": { + "title": "The power supply needs to be replaced", + "description": "Press SUBMIT to confirm the power supply has been replaced" + } + } + } + }, "out_of_blinker_fluid": { "title": "The blinker fluid is empty and needs to be refilled", "fix_flow": { "step": { "confirm": { "title": "Blinker fluid needs to be refilled", - "description": "Press OK when blinker fluid has been refilled" + "description": "Press SUBMIT when blinker fluid has been refilled" } } } diff --git a/homeassistant/components/demo/translations/en.json b/homeassistant/components/demo/translations/en.json index 11378fb94d4..a98f0d3c28d 100644 --- a/homeassistant/components/demo/translations/en.json +++ b/homeassistant/components/demo/translations/en.json @@ -1,10 +1,21 @@ { "issues": { + "bad_psu": { + "fix_flow": { + "step": { + "confirm": { + "description": "Press SUBMIT to confirm the power supply has been replaced", + "title": "The power supply needs to be replaced" + } + } + }, + "title": "The power supply is not stable" + }, "out_of_blinker_fluid": { "fix_flow": { "step": { "confirm": { - "description": "Press OK when blinker fluid has been refilled", + "description": "Press SUBMIT when blinker fluid has been refilled", "title": "Blinker fluid needs to be refilled" } } diff --git a/homeassistant/components/repairs/__init__.py b/homeassistant/components/repairs/__init__.py index 4471def0dcd..5014baff834 100644 --- a/homeassistant/components/repairs/__init__.py +++ b/homeassistant/components/repairs/__init__.py @@ -7,6 +7,7 @@ from homeassistant.helpers.typing import ConfigType from . import issue_handler, websocket_api from .const import DOMAIN from .issue_handler import ( + ConfirmRepairFlow, async_create_issue, async_delete_issue, create_issue, @@ -21,6 +22,7 @@ __all__ = [ "create_issue", "delete_issue", "DOMAIN", + "ConfirmRepairFlow", "IssueSeverity", "RepairsFlow", ] diff --git a/homeassistant/components/repairs/issue_handler.py b/homeassistant/components/repairs/issue_handler.py index a08fff29598..23f37754ffe 100644 --- a/homeassistant/components/repairs/issue_handler.py +++ b/homeassistant/components/repairs/issue_handler.py @@ -5,6 +5,7 @@ import functools as ft from typing import Any from awesomeversion import AwesomeVersion, AwesomeVersionStrategy +import voluptuous as vol from homeassistant import data_entry_flow from homeassistant.core import HomeAssistant, callback @@ -19,6 +20,26 @@ from .issue_registry import async_get as async_get_issue_registry from .models import IssueSeverity, RepairsFlow, RepairsProtocol +class ConfirmRepairFlow(RepairsFlow): + """Handler for an issue fixing flow without any side effects.""" + + 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="", data={}) + + return self.async_show_form(step_id="confirm", data_schema=vol.Schema({})) + + class RepairsFlowManager(data_entry_flow.FlowManager): """Manage repairs flows.""" @@ -30,14 +51,6 @@ class RepairsFlowManager(data_entry_flow.FlowManager): data: dict[str, Any] | None = None, ) -> RepairsFlow: """Create a flow. platform is a repairs module.""" - if "platforms" not in self.hass.data[DOMAIN]: - await async_process_repairs_platforms(self.hass) - - platforms: dict[str, RepairsProtocol] = 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"] @@ -46,6 +59,14 @@ class RepairsFlowManager(data_entry_flow.FlowManager): if issue is None or not issue.is_fixable: raise data_entry_flow.UnknownStep + if "platforms" not in self.hass.data[DOMAIN]: + await async_process_repairs_platforms(self.hass) + + platforms: dict[str, RepairsProtocol] = self.hass.data[DOMAIN]["platforms"] + if handler_key not in platforms: + return ConfirmRepairFlow() + platform = platforms[handler_key] + return await platform.async_create_fix_flow(self.hass, issue_id) async def async_finish_flow( diff --git a/tests/components/demo/test_init.py b/tests/components/demo/test_init.py index 5b322cb776f..ba8baa0d487 100644 --- a/tests/components/demo/test_init.py +++ b/tests/components/demo/test_init.py @@ -129,6 +129,20 @@ async def test_issues_created(hass, hass_client, hass_ws_client): "translation_key": "unfixable_problem", "translation_placeholders": None, }, + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": "demo", + "ignored": False, + "is_fixable": True, + "issue_domain": None, + "issue_id": "bad_psu", + "learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU", + "severity": "critical", + "translation_key": "bad_psu", + "translation_placeholders": None, + }, ] } @@ -164,7 +178,7 @@ async def test_issues_created(hass, hass_client, hass_ws_client): "description_placeholders": None, "flow_id": flow_id, "handler": "demo", - "title": "Fixed issue", + "title": "", "type": "create_entry", "version": 1, } @@ -203,5 +217,19 @@ async def test_issues_created(hass, hass_client, hass_ws_client): "translation_key": "unfixable_problem", "translation_placeholders": None, }, + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": "demo", + "ignored": False, + "is_fixable": True, + "issue_domain": None, + "issue_id": "bad_psu", + "learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU", + "severity": "critical", + "translation_key": "bad_psu", + "translation_placeholders": None, + }, ] } diff --git a/tests/components/repairs/test_websocket_api.py b/tests/components/repairs/test_websocket_api.py index 359024f9fe5..a47a7a899ea 100644 --- a/tests/components/repairs/test_websocket_api.py +++ b/tests/components/repairs/test_websocket_api.py @@ -21,21 +21,24 @@ from homeassistant.setup import async_setup_component from tests.common import mock_platform +DEFAULT_ISSUES = [ + { + "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", + "translation_placeholders": {"abc": "123"}, + } +] -async def create_issues(hass, ws_client): + +async def create_issues(hass, ws_client, issues=None): """Create issues.""" - issues = [ - { - "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", - "translation_placeholders": {"abc": "123"}, - }, - ] + if issues is None: + issues = DEFAULT_ISSUES for issue in issues: async_create_issue( @@ -79,23 +82,22 @@ class MockFixFlow(RepairsFlow): ) -> data_entry_flow.FlowResult: """Handle the first step of a fix flow.""" - return await (self.async_step_confirm()) + return await (self.async_step_custom_step()) - async def async_step_confirm( + async def async_step_custom_step( self, user_input: dict[str, str] | None = None ) -> data_entry_flow.FlowResult: - """Handle the confirm step of a fix flow.""" + """Handle a custom_step step of a fix flow.""" if user_input is not None: - return self.async_create_entry(title=None, data=None) + return self.async_create_entry(title="", data={}) - return self.async_show_form(step_id="confirm", data_schema=vol.Schema({})) + return self.async_show_form(step_id="custom_step", data_schema=vol.Schema({})) @pytest.fixture(autouse=True) async def mock_repairs_integration(hass): """Mock a repairs integration.""" hass.config.components.add("fake_integration") - hass.config.components.add("integration_without_diagnostics") def async_create_fix_flow(hass, issue_id): return MockFixFlow() @@ -107,7 +109,7 @@ async def mock_repairs_integration(hass): ) mock_platform( hass, - "integration_without_diagnostics.repairs", + "integration_without_repairs.repairs", Mock(spec=[]), ) @@ -237,7 +239,16 @@ async def test_fix_non_existing_issue( } -async def test_fix_issue(hass: HomeAssistant, hass_client, hass_ws_client) -> None: +@pytest.mark.parametrize( + "domain, step", + ( + ("fake_integration", "custom_step"), + ("fake_integration_default_handler", "confirm"), + ), +) +async def test_fix_issue( + hass: HomeAssistant, hass_client, hass_ws_client, domain, step +) -> None: """Test we can fix an issue.""" assert await async_setup_component(hass, "http", {}) assert await async_setup_component(hass, DOMAIN, {}) @@ -245,12 +256,11 @@ async def test_fix_issue(hass: HomeAssistant, hass_client, hass_ws_client) -> No ws_client = await hass_ws_client(hass) client = await hass_client() - await create_issues(hass, ws_client) + issues = [{**DEFAULT_ISSUES[0], "domain": domain}] + await create_issues(hass, ws_client, issues=issues) url = "/api/repairs/issues/fix" - resp = await client.post( - url, json={"handler": "fake_integration", "issue_id": "issue_1"} - ) + resp = await client.post(url, json={"handler": domain, "issue_id": "issue_1"}) assert resp.status == HTTPStatus.OK data = await resp.json() @@ -261,9 +271,9 @@ async def test_fix_issue(hass: HomeAssistant, hass_client, hass_ws_client) -> No "description_placeholders": None, "errors": None, "flow_id": ANY, - "handler": "fake_integration", + "handler": domain, "last_step": None, - "step_id": "confirm", + "step_id": step, "type": "form", } @@ -286,8 +296,8 @@ async def test_fix_issue(hass: HomeAssistant, hass_client, hass_ws_client) -> No "description": None, "description_placeholders": None, "flow_id": flow_id, - "handler": "fake_integration", - "title": None, + "handler": domain, + "title": "", "type": "create_entry", "version": 1, }