From d92516b7c9f99bed9ba453e937d18583d5c3da73 Mon Sep 17 00:00:00 2001 From: Branden Cash <203336+ammmze@users.noreply.github.com> Date: Mon, 29 Dec 2025 13:52:25 -0700 Subject: [PATCH] Implement reconfigure config flow in SRP energy (#151542) Co-authored-by: Joostlek --- .../components/srp_energy/config_flow.py | 104 ++++++++++------ .../components/srp_energy/strings.json | 1 + tests/components/srp_energy/conftest.py | 9 ++ .../components/srp_energy/test_config_flow.py | 116 +++++++++++++++++- 4 files changed, 189 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/srp_energy/config_flow.py b/homeassistant/components/srp_energy/config_flow.py index a91b1f46b40..9e32d935e80 100644 --- a/homeassistant/components/srp_energy/config_flow.py +++ b/homeassistant/components/srp_energy/config_flow.py @@ -7,9 +7,14 @@ from typing import Any from srpenergy.client import SrpEnergyClient import voluptuous as vol -from homeassistant.config_entries import ConfigFlow, ConfigFlowResult +from homeassistant.config_entries import ( + SOURCE_RECONFIGURE, + SOURCE_USER, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import CONF_ID, CONF_NAME, CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from .const import CONF_IS_TOU, DOMAIN, LOGGER @@ -40,52 +45,71 @@ class SRPEnergyConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 - @callback - def _show_form(self, errors: dict[str, Any]) -> ConfigFlowResult: - """Show the form to the user.""" - LOGGER.debug("Show Form") - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Required( - CONF_NAME, default=self.hass.config.location_name - ): str, - vol.Required(CONF_ID): str, - vol.Required(CONF_USERNAME): str, - vol.Required(CONF_PASSWORD): str, - vol.Optional(CONF_IS_TOU, default=False): bool, - } - ), - errors=errors, - ) - async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: """Handle a flow initialized by the user.""" LOGGER.debug("Config entry") errors: dict[str, str] = {} - if not user_input: - return self._show_form(errors) + if user_input: + try: + await validate_input(self.hass, user_input) + except ValueError: + # Thrown when the account id is malformed + errors["base"] = "invalid_account" + except InvalidAuth: + errors["base"] = "invalid_auth" + except Exception: # noqa: BLE001 + LOGGER.exception("Unexpected exception") + return self.async_abort(reason="unknown") + else: + await self.async_set_unique_id(user_input[CONF_ID]) + if self.source == SOURCE_USER: + self._abort_if_unique_id_configured() + if self.source == SOURCE_RECONFIGURE: + self._abort_if_unique_id_mismatch() - try: - await validate_input(self.hass, user_input) - except ValueError: - # Thrown when the account id is malformed - errors["base"] = "invalid_account" - return self._show_form(errors) - except InvalidAuth: - errors["base"] = "invalid_auth" - return self._show_form(errors) - except Exception: # noqa: BLE001 - LOGGER.exception("Unexpected exception") - return self.async_abort(reason="unknown") + if self.source == SOURCE_USER: + return self.async_create_entry( + title=user_input[CONF_NAME], + data=user_input, + ) + return self.async_update_reload_and_abort( + self._get_reconfigure_entry(), + data=user_input, + ) + return self.async_show_form( + step_id="user", + data_schema=self.add_suggested_values_to_schema( + data_schema=vol.Schema( + { + vol.Required(CONF_ID): ( + str + if self.source == SOURCE_USER + else self._get_reconfigure_entry().data[CONF_ID] + ), + vol.Required( + CONF_NAME, default=self.hass.config.location_name + ): str, + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Optional(CONF_IS_TOU, default=False): bool, + } + ), + suggested_values=( + user_input or self._get_reconfigure_entry().data + if self.source == SOURCE_RECONFIGURE + else None + ), + ), + errors=errors, + ) - await self.async_set_unique_id(user_input[CONF_ID]) - self._abort_if_unique_id_configured() - - return self.async_create_entry(title=user_input[CONF_NAME], data=user_input) + async def async_step_reconfigure( + self, user_input: dict[str, Any] + ) -> ConfigFlowResult: + """Handle reconfiguration.""" + return await self.async_step_user() class InvalidAuth(HomeAssistantError): diff --git a/homeassistant/components/srp_energy/strings.json b/homeassistant/components/srp_energy/strings.json index cdea706e1f9..c8321dc416a 100644 --- a/homeassistant/components/srp_energy/strings.json +++ b/homeassistant/components/srp_energy/strings.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]", "unknown": "[%key:common::config_flow::error::unknown%]" }, "error": { diff --git a/tests/components/srp_energy/conftest.py b/tests/components/srp_energy/conftest.py index b1d5b958d47..b305c7868a8 100644 --- a/tests/components/srp_energy/conftest.py +++ b/tests/components/srp_energy/conftest.py @@ -88,3 +88,12 @@ async def init_integration( await hass.async_block_till_done() return mock_config_entry + + +@pytest.fixture +def mock_setup_entry() -> Generator[MagicMock]: + """Mock async_setup_entry.""" + with patch( + "homeassistant.components.srp_energy.async_setup_entry", return_value=True + ) as mock_setup_entry: + yield mock_setup_entry diff --git a/tests/components/srp_energy/test_config_flow.py b/tests/components/srp_energy/test_config_flow.py index e3abb3c98df..27c85f30cd5 100644 --- a/tests/components/srp_energy/test_config_flow.py +++ b/tests/components/srp_energy/test_config_flow.py @@ -6,7 +6,13 @@ import pytest from homeassistant.components.srp_energy.const import CONF_IS_TOU, DOMAIN from homeassistant.config_entries import SOURCE_USER, ConfigEntryState -from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_SOURCE, CONF_USERNAME +from homeassistant.const import ( + CONF_ID, + CONF_NAME, + CONF_PASSWORD, + CONF_SOURCE, + CONF_USERNAME, +) from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType @@ -172,3 +178,111 @@ async def test_flow_multiple_configs( entries = hass.config_entries.async_entries() domain_entries = [entry for entry in entries if entry.domain == DOMAIN] assert len(domain_entries) == 2 + + +async def test_reconfigure( + hass: HomeAssistant, init_integration: MockConfigEntry +) -> None: + """Test reconfiguring an existing entry.""" + + result = await init_integration.start_reconfigure_flow(hass) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_ID: ACCNT_ID, + CONF_NAME: ACCNT_NAME + "reconf", + CONF_USERNAME: ACCNT_USERNAME + "reconf", + CONF_PASSWORD: ACCNT_PASSWORD + "reconf", + CONF_IS_TOU: not ACCNT_IS_TOU, + }, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert init_integration.data == { + CONF_ID: ACCNT_ID, + CONF_NAME: ACCNT_NAME + "reconf", + CONF_USERNAME: ACCNT_USERNAME + "reconf", + CONF_PASSWORD: ACCNT_PASSWORD + "reconf", + CONF_IS_TOU: not ACCNT_IS_TOU, + } + + +async def test_reconfigure_error( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_srp_energy_config_flow: MagicMock, + mock_setup_entry: MagicMock, +) -> None: + """Test reconfiguring an existing entry.""" + + result = await init_integration.start_reconfigure_flow(hass) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + mock_srp_energy_config_flow.validate.side_effect = ValueError + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_ID: ACCNT_ID, + CONF_NAME: ACCNT_NAME + "reconf", + CONF_USERNAME: ACCNT_USERNAME + "reconf", + CONF_PASSWORD: ACCNT_PASSWORD + "reconf", + CONF_IS_TOU: not ACCNT_IS_TOU, + }, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": "invalid_account"} + + mock_srp_energy_config_flow.validate.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_ID: ACCNT_ID, + CONF_NAME: ACCNT_NAME + "reconf", + CONF_USERNAME: ACCNT_USERNAME + "reconf", + CONF_PASSWORD: ACCNT_PASSWORD + "reconf", + CONF_IS_TOU: not ACCNT_IS_TOU, + }, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + +async def test_reconfigure_unknown_error( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_srp_energy_config_flow: MagicMock, + mock_setup_entry: MagicMock, +) -> None: + """Test reconfiguring an existing entry and handling unknown error.""" + + result = await init_integration.start_reconfigure_flow(hass) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "user" + + mock_srp_energy_config_flow.validate.side_effect = Exception + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_ID: ACCNT_ID, + CONF_NAME: ACCNT_NAME + "reconf", + CONF_USERNAME: ACCNT_USERNAME + "reconf", + CONF_PASSWORD: ACCNT_PASSWORD + "reconf", + CONF_IS_TOU: not ACCNT_IS_TOU, + }, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "unknown"