diff --git a/homeassistant/components/smtp/config_flow.py b/homeassistant/components/smtp/config_flow.py index b38cfef48c85..f298198f588a 100644 --- a/homeassistant/components/smtp/config_flow.py +++ b/homeassistant/components/smtp/config_flow.py @@ -10,6 +10,7 @@ from typing import Any, override import voluptuous as vol +from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN from homeassistant.config_entries import ( SOURCE_USER, ConfigFlow, @@ -33,7 +34,7 @@ from homeassistant.const import ( UnitOfTime, ) from homeassistant.core import callback -from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers.selector import ( NumberSelector, NumberSelectorConfig, @@ -359,6 +360,55 @@ class RecipientSubentryFlowHandler(ConfigSubentryFlow): ), ) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> SubentryFlowResult: + """Reconfigure flow to update a recipient.""" + + entry = self._get_entry() + subentry = self._get_reconfigure_subentry() + + if user_input is not None: + old_unique_id = subentry.unique_id + result = self.async_update_and_abort( + entry, + subentry=subentry, + title=( + user_input[CONF_RECIPIENT] + if subentry.title == old_unique_id + else subentry.title + ), + data_updates={}, + unique_id=user_input[CONF_RECIPIENT], + ) + if result.get("reason") == "reconfigure_successful" and ( + entity := er.async_get(self.hass).async_get_entity_id( + NOTIFY_DOMAIN, DOMAIN, f"{entry.entry_id}_{old_unique_id}" + ) + ): + er.async_get(self.hass).async_update_entity( + entity, + new_unique_id=f"{entry.entry_id}_{user_input[CONF_RECIPIENT]}", + ) + return result + + return self.async_show_form( + step_id="reconfigure", + data_schema=self.add_suggested_values_to_schema( + data_schema=vol.Schema( + { + vol.Required(CONF_RECIPIENT): TextSelector( + TextSelectorConfig( + type=TextSelectorType.EMAIL, + autocomplete="email", + ), + ) + } + ), + suggested_values={CONF_RECIPIENT: subentry.unique_id}, + ), + ) + class OptionsFlowHandler(OptionsFlow): """Handle options flow.""" diff --git a/homeassistant/components/smtp/strings.json b/homeassistant/components/smtp/strings.json index 1c6a8cf6c2c5..9908b7c9f521 100644 --- a/homeassistant/components/smtp/strings.json +++ b/homeassistant/components/smtp/strings.json @@ -74,13 +74,24 @@ "config_subentries": { "recipient": { "abort": { - "already_configured": "Recipient is already configured" + "already_configured": "Recipient is already configured", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" }, "entry_type": "Recipient", "initiate_flow": { "user": "Add recipient" }, "step": { + "reconfigure": { + "data": { + "recipient": "[%key:common::config_flow::data::email%]" + }, + "data_description": { + "recipient": "[%key:component::smtp::config_subentries::recipient::step::user::data_description::recipient%]" + }, + "description": "Update the recipient email address.", + "title": "Reconfigure recipient" + }, "user": { "data": { "name": "[%key:common::config_flow::data::name%]", diff --git a/tests/components/smtp/test_config_flow.py b/tests/components/smtp/test_config_flow.py index 0767acbe0546..fd8f82d13e21 100644 --- a/tests/components/smtp/test_config_flow.py +++ b/tests/components/smtp/test_config_flow.py @@ -13,7 +13,13 @@ from homeassistant.components.smtp.const import ( DOMAIN, SUBENTRY_TYPE_RECIPIENT, ) -from homeassistant.config_entries import SOURCE_USER, ConfigEntryState, FlowType +from homeassistant.config_entries import ( + SOURCE_RECONFIGURE, + SOURCE_USER, + ConfigEntryState, + ConfigSubentryData, + FlowType, +) from homeassistant.const import ( CONF_NAME, CONF_PASSWORD, @@ -24,6 +30,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType +from homeassistant.helpers import entity_registry as er from .conftest import USER_INPUT @@ -442,3 +449,136 @@ async def test_form_reauth_errors( CONF_PASSWORD: "new-password", } assert len(hass.config_entries.async_entries()) == 1 + + +@pytest.mark.usefixtures("smtp") +async def test_form_subentry_reconfigure( + hass: HomeAssistant, + config_entry: MockConfigEntry, + entity_registry: er.EntityRegistry, +) -> None: + """Test subentry reconfigure flow.""" + + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert ( + len(er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)) + == 1 + ) + assert (entity := entity_registry.async_get("notify.home_assistant_recipient")) + assert entity.unique_id == "123456789_recipient@example.com" + + result = await config_entry.start_subentry_reconfigure_flow(hass, "ABCDEF") + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == SOURCE_RECONFIGURE + + result = await hass.config_entries.subentries.async_configure( + result["flow_id"], + user_input={CONF_RECIPIENT: "changed@example.com"}, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + assert config_entry.subentries["ABCDEF"].unique_id == "changed@example.com" + assert ( + len(er.async_entries_for_config_entry(entity_registry, config_entry.entry_id)) + == 1 + ) + assert (entity := entity_registry.async_get("notify.home_assistant_recipient")) + assert entity.unique_id == "123456789_changed@example.com" + + +@pytest.mark.usefixtures("smtp") +async def test_form_subentry_reconfigure_already_configured( + hass: HomeAssistant, +) -> None: + """Test we abort subentry reconfigure flow when already configured.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + title="Home Assistant", + data=USER_INPUT, + options={ + CONF_TIMEOUT: 5, + }, + entry_id="123456789", + subentries_data=[ + ConfigSubentryData( + data={}, + subentry_id="ABCDEF", + subentry_type=SUBENTRY_TYPE_RECIPIENT, + title="Recipient", + unique_id="recipient@example.com", + ), + ConfigSubentryData( + data={}, + subentry_id="GHIJKL", + subentry_type=SUBENTRY_TYPE_RECIPIENT, + title="Recipient2", + unique_id="changed@example.com", + ), + ], + ) + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + result = await config_entry.start_subentry_reconfigure_flow(hass, "ABCDEF") + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == SOURCE_RECONFIGURE + + result = await hass.config_entries.subentries.async_configure( + result["flow_id"], + user_input={CONF_RECIPIENT: "changed@example.com"}, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + +@pytest.mark.usefixtures("smtp") +async def test_form_subentry_reconfigure_updates_title( + hass: HomeAssistant, +) -> None: + """Test subentry reconfigure flow updates subentry title if it matches email.""" + config_entry = MockConfigEntry( + domain=DOMAIN, + title="Home Assistant", + data=USER_INPUT, + options={ + CONF_TIMEOUT: 5, + }, + entry_id="123456789", + subentries_data=[ + ConfigSubentryData( + data={}, + subentry_id="ABCDEF", + subentry_type=SUBENTRY_TYPE_RECIPIENT, + title="recipient@example.com", + unique_id="recipient@example.com", + ) + ], + ) + + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + result = await config_entry.start_subentry_reconfigure_flow(hass, "ABCDEF") + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == SOURCE_RECONFIGURE + + result = await hass.config_entries.subentries.async_configure( + result["flow_id"], + user_input={CONF_RECIPIENT: "changed@example.com"}, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + assert config_entry.subentries["ABCDEF"].unique_id == "changed@example.com" + assert config_entry.subentries["ABCDEF"].title == "changed@example.com"