diff --git a/homeassistant/components/namecheapdns/config_flow.py b/homeassistant/components/namecheapdns/config_flow.py index 28af30298041..2f25d01bd628 100644 --- a/homeassistant/components/namecheapdns/config_flow.py +++ b/homeassistant/components/namecheapdns/config_flow.py @@ -9,7 +9,7 @@ from aiohttp import ClientError import voluptuous as vol from homeassistant.config_entries import ConfigFlow, ConfigFlowResult -from homeassistant.const import CONF_DOMAIN, CONF_HOST, CONF_PASSWORD +from homeassistant.const import CONF_DOMAIN, CONF_HOST, CONF_NAME, CONF_PASSWORD from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.selector import ( @@ -37,6 +37,16 @@ STEP_USER_DATA_SCHEMA = vol.Schema( } ) +STEP_RECONFIGURE_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_PASSWORD): TextSelector( + TextSelectorConfig( + type=TextSelectorType.PASSWORD, autocomplete="current-password" + ) + ), + } +) + class NamecheapDnsConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for Namecheap DynamicDNS.""" @@ -89,3 +99,41 @@ class NamecheapDnsConfigFlow(ConfigFlow, domain=DOMAIN): deprecate_yaml_issue(self.hass, import_success=True) return result + + 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: + session = async_get_clientsession(self.hass) + try: + if not await update_namecheapdns( + session, + entry.data[CONF_HOST], + entry.data[CONF_DOMAIN], + user_input[CONF_PASSWORD], + ): + errors["base"] = "update_failed" + except ClientError: + _LOGGER.debug("Cannot connect", exc_info=True) + errors["base"] = "cannot_connect" + except Exception: + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + if not errors: + return self.async_update_reload_and_abort( + entry, + data_updates=user_input, + ) + + return self.async_show_form( + step_id="reconfigure", + data_schema=STEP_RECONFIGURE_DATA_SCHEMA, + errors=errors, + description_placeholders={CONF_NAME: entry.title}, + ) diff --git a/homeassistant/components/namecheapdns/strings.json b/homeassistant/components/namecheapdns/strings.json index 26c130fa2899..b419c6261b8c 100644 --- a/homeassistant/components/namecheapdns/strings.json +++ b/homeassistant/components/namecheapdns/strings.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", @@ -9,6 +10,15 @@ "update_failed": "Updating DNS failed" }, "step": { + "reconfigure": { + "data": { + "password": "[%key:component::namecheapdns::config::step::user::data::password%]" + }, + "data_description": { + "password": "[%key:component::namecheapdns::config::step::user::data_description::password%]" + }, + "title": "Re-configure {name}" + }, "user": { "data": { "domain": "[%key:common::config_flow::data::username%]", diff --git a/tests/components/namecheapdns/test_config_flow.py b/tests/components/namecheapdns/test_config_flow.py index a56ea9448526..1791086c57df 100644 --- a/tests/components/namecheapdns/test_config_flow.py +++ b/tests/components/namecheapdns/test_config_flow.py @@ -7,6 +7,7 @@ import pytest from homeassistant.components.namecheapdns.const import DOMAIN from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_PASSWORD from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers import issue_registry as ir @@ -14,6 +15,8 @@ from homeassistant.setup import async_setup_component from .conftest import TEST_USER_INPUT +from tests.common import MockConfigEntry + @pytest.mark.usefixtures("mock_namecheap") async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None: @@ -140,3 +143,68 @@ async def test_init_import_flow( ) assert len(mock_setup_entry.mock_calls) == 1 assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + + +@pytest.mark.usefixtures("mock_namecheap") +async def test_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_PASSWORD: "new-password"} + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert config_entry.data[CONF_PASSWORD] == "new-password" + + +@pytest.mark.parametrize( + ("side_effect", "text_error"), + [ + (ValueError, "unknown"), + (False, "update_failed"), + (ClientError, "cannot_connect"), + ], +) +async def test_reconfigure_errors( + hass: HomeAssistant, + config_entry: MockConfigEntry, + mock_namecheap: AsyncMock, + side_effect: Exception | bool, + text_error: str, +) -> None: + """Test we handle errors.""" + + 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_namecheap.side_effect = [side_effect] + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_PASSWORD: "new-password"} + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": text_error} + + mock_namecheap.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_PASSWORD: "new-password"} + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + assert config_entry.data[CONF_PASSWORD] == "new-password"