diff --git a/homeassistant/components/uptime_kuma/config_flow.py b/homeassistant/components/uptime_kuma/config_flow.py index 30f9d7ae9ba..da71084d1bc 100644 --- a/homeassistant/components/uptime_kuma/config_flow.py +++ b/homeassistant/components/uptime_kuma/config_flow.py @@ -16,6 +16,7 @@ from yarl import URL from homeassistant.config_entries import ConfigFlow, ConfigFlowResult from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL +from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import ( TextSelector, @@ -42,6 +43,29 @@ STEP_USER_DATA_SCHEMA = vol.Schema( STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Optional(CONF_API_KEY, default=""): str}) +async def validate_connection( + hass: HomeAssistant, + url: URL | str, + verify_ssl: bool, + api_key: str, +) -> dict[str, str]: + """Validate Uptime Kuma connectivity.""" + errors: dict[str, str] = {} + session = async_get_clientsession(hass, verify_ssl) + uptime_kuma = UptimeKuma(session, url, api_key) + + try: + await uptime_kuma.metrics() + except UptimeKumaAuthenticationException: + errors["base"] = "invalid_auth" + except UptimeKumaException: + errors["base"] = "cannot_connect" + except Exception: + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + return errors + + class UptimeKumaConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Uptime Kuma.""" @@ -54,19 +78,14 @@ class UptimeKumaConfigFlow(ConfigFlow, domain=DOMAIN): url = URL(user_input[CONF_URL]) self._async_abort_entries_match({CONF_URL: url.human_repr()}) - session = async_get_clientsession(self.hass, user_input[CONF_VERIFY_SSL]) - uptime_kuma = UptimeKuma(session, url, user_input[CONF_API_KEY]) - - try: - await uptime_kuma.metrics() - except UptimeKumaAuthenticationException: - errors["base"] = "invalid_auth" - except UptimeKumaException: - errors["base"] = "cannot_connect" - except Exception: - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - else: + if not ( + errors := await validate_connection( + self.hass, + url, + user_input[CONF_VERIFY_SSL], + user_input[CONF_API_KEY], + ) + ): return self.async_create_entry( title=url.host or "", data={**user_input, CONF_URL: url.human_repr()}, @@ -95,23 +114,14 @@ class UptimeKumaConfigFlow(ConfigFlow, domain=DOMAIN): entry = self._get_reauth_entry() if user_input is not None: - session = async_get_clientsession(self.hass, entry.data[CONF_VERIFY_SSL]) - uptime_kuma = UptimeKuma( - session, - entry.data[CONF_URL], - user_input[CONF_API_KEY], - ) - - try: - await uptime_kuma.metrics() - except UptimeKumaAuthenticationException: - errors["base"] = "invalid_auth" - except UptimeKumaException: - errors["base"] = "cannot_connect" - except Exception: - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - else: + if not ( + errors := await validate_connection( + self.hass, + entry.data[CONF_URL], + entry.data[CONF_VERIFY_SSL], + user_input[CONF_API_KEY], + ) + ): return self.async_update_reload_and_abort( entry, data_updates=user_input, @@ -124,3 +134,37 @@ class UptimeKumaConfigFlow(ConfigFlow, domain=DOMAIN): ), errors=errors, ) + + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle reconfigure flow.""" + errors: dict[str, str] = {} + + entry = self._get_reconfigure_entry() + + if user_input is not None: + url = URL(user_input[CONF_URL]) + self._async_abort_entries_match({CONF_URL: url.human_repr()}) + + if not ( + errors := await validate_connection( + self.hass, + url, + user_input[CONF_VERIFY_SSL], + user_input[CONF_API_KEY], + ) + ): + return self.async_update_reload_and_abort( + entry, + data_updates={**user_input, CONF_URL: url.human_repr()}, + ) + + return self.async_show_form( + step_id="reconfigure", + data_schema=self.add_suggested_values_to_schema( + data_schema=STEP_USER_DATA_SCHEMA, + suggested_values=user_input or entry.data, + ), + errors=errors, + ) diff --git a/homeassistant/components/uptime_kuma/quality_scale.yaml b/homeassistant/components/uptime_kuma/quality_scale.yaml index 469ecad8d7b..876318c8917 100644 --- a/homeassistant/components/uptime_kuma/quality_scale.yaml +++ b/homeassistant/components/uptime_kuma/quality_scale.yaml @@ -66,7 +66,7 @@ rules: entity-translations: done exception-translations: done icon-translations: done - reconfiguration-flow: todo + reconfiguration-flow: done repair-issues: status: exempt comment: has no repairs diff --git a/homeassistant/components/uptime_kuma/strings.json b/homeassistant/components/uptime_kuma/strings.json index 0321db1c221..87dcf6e8cf7 100644 --- a/homeassistant/components/uptime_kuma/strings.json +++ b/homeassistant/components/uptime_kuma/strings.json @@ -23,6 +23,19 @@ "data_description": { "api_key": "[%key:component::uptime_kuma::config::step::user::data_description::api_key%]" } + }, + "reconfigure": { + "title": "Update configuration for Uptime Kuma", + "data": { + "url": "[%key:common::config_flow::data::url%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]", + "api_key": "[%key:common::config_flow::data::api_key%]" + }, + "data_description": { + "url": "[%key:component::uptime_kuma::config::step::user::data_description::url%]", + "verify_ssl": "[%key:component::uptime_kuma::config::step::user::data_description::verify_ssl%]", + "api_key": "[%key:component::uptime_kuma::config::step::user::data_description::api_key%]" + } } }, "error": { @@ -32,7 +45,8 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" } }, "entity": { diff --git a/tests/components/uptime_kuma/test_config_flow.py b/tests/components/uptime_kuma/test_config_flow.py index 3c1bf902ce8..ab695107b9b 100644 --- a/tests/components/uptime_kuma/test_config_flow.py +++ b/tests/components/uptime_kuma/test_config_flow.py @@ -190,3 +190,93 @@ async def test_flow_reauth_errors( assert config_entry.data[CONF_API_KEY] == "newapikey" assert len(hass.config_entries.async_entries()) == 1 + + +@pytest.mark.usefixtures("mock_pythonkuma") +async def test_flow_reconfigure( + hass: HomeAssistant, + config_entry: MockConfigEntry, +) -> None: + """Test reconfigure flow.""" + config_entry.add_to_hass(hass) + result = await config_entry.start_reconfigure_flow(hass) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "reconfigure" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_URL: "https://uptime.example.org:3001/", + CONF_VERIFY_SSL: False, + CONF_API_KEY: "newapikey", + }, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert config_entry.data == { + CONF_URL: "https://uptime.example.org:3001/", + CONF_VERIFY_SSL: False, + CONF_API_KEY: "newapikey", + } + + assert len(hass.config_entries.async_entries()) == 1 + + +@pytest.mark.parametrize( + ("raise_error", "text_error"), + [ + (UptimeKumaConnectionException, "cannot_connect"), + (UptimeKumaAuthenticationException, "invalid_auth"), + (ValueError, "unknown"), + ], +) +@pytest.mark.usefixtures("mock_pythonkuma") +async def test_flow_reconfigure_errors( + hass: HomeAssistant, + config_entry: MockConfigEntry, + mock_pythonkuma: AsyncMock, + raise_error: Exception, + text_error: str, +) -> None: + """Test reconfigure flow errors and recover.""" + config_entry.add_to_hass(hass) + result = await config_entry.start_reconfigure_flow(hass) + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "reconfigure" + + mock_pythonkuma.metrics.side_effect = raise_error + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_URL: "https://uptime.example.org:3001/", + CONF_VERIFY_SSL: False, + CONF_API_KEY: "newapikey", + }, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": text_error} + + mock_pythonkuma.metrics.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_URL: "https://uptime.example.org:3001/", + CONF_VERIFY_SSL: False, + CONF_API_KEY: "newapikey", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert config_entry.data == { + CONF_URL: "https://uptime.example.org:3001/", + CONF_VERIFY_SSL: False, + CONF_API_KEY: "newapikey", + } + + assert len(hass.config_entries.async_entries()) == 1