diff --git a/homeassistant/components/powerfox/config_flow.py b/homeassistant/components/powerfox/config_flow.py index ca78b8eb874..dd17badf881 100644 --- a/homeassistant/components/powerfox/config_flow.py +++ b/homeassistant/components/powerfox/config_flow.py @@ -100,3 +100,36 @@ class PowerfoxConfigFlow(ConfigFlow, domain=DOMAIN): data_schema=STEP_REAUTH_SCHEMA, errors=errors, ) + + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Reconfigure Powerfox configuration.""" + errors = {} + + reconfigure_entry = self._get_reconfigure_entry() + if user_input is not None: + client = Powerfox( + username=user_input[CONF_EMAIL], + password=user_input[CONF_PASSWORD], + session=async_get_clientsession(self.hass), + ) + try: + await client.all_devices() + except PowerfoxAuthenticationError: + errors["base"] = "invalid_auth" + except PowerfoxConnectionError: + errors["base"] = "cannot_connect" + else: + if reconfigure_entry.data[CONF_EMAIL] != user_input[CONF_EMAIL]: + self._async_abort_entries_match( + {CONF_EMAIL: user_input[CONF_EMAIL]} + ) + return self.async_update_reload_and_abort( + reconfigure_entry, data_updates=user_input + ) + return self.async_show_form( + step_id="reconfigure", + data_schema=STEP_USER_DATA_SCHEMA, + errors=errors, + ) diff --git a/homeassistant/components/powerfox/quality_scale.yaml b/homeassistant/components/powerfox/quality_scale.yaml index 7e104b894ca..f72d25c3684 100644 --- a/homeassistant/components/powerfox/quality_scale.yaml +++ b/homeassistant/components/powerfox/quality_scale.yaml @@ -80,7 +80,7 @@ rules: status: exempt comment: | There is no need for icon translations. - reconfiguration-flow: todo + reconfiguration-flow: done repair-issues: status: exempt comment: | diff --git a/homeassistant/components/powerfox/strings.json b/homeassistant/components/powerfox/strings.json index 3eab77494d3..4a7c8e8fa4d 100644 --- a/homeassistant/components/powerfox/strings.json +++ b/homeassistant/components/powerfox/strings.json @@ -21,6 +21,18 @@ "data_description": { "password": "[%key:component::powerfox::config::step::user::data_description::password%]" } + }, + "reconfigure": { + "title": "Reconfigure your Powerfox account", + "description": "Powerfox is already configured. Would you like to reconfigure it?", + "data": { + "email": "[%key:common::config_flow::data::email%]", + "password": "[%key:common::config_flow::data::password%]" + }, + "data_description": { + "email": "[%key:component::powerfox::config::step::user::data_description::email%]", + "password": "[%key:component::powerfox::config::step::user::data_description::password%]" + } } }, "error": { @@ -29,7 +41,8 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", - "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/powerfox/test_config_flow.py b/tests/components/powerfox/test_config_flow.py index 759092aee6e..a38f316faf3 100644 --- a/tests/components/powerfox/test_config_flow.py +++ b/tests/components/powerfox/test_config_flow.py @@ -110,6 +110,32 @@ async def test_duplicate_entry( assert result.get("reason") == "already_configured" +async def test_duplicate_entry_reconfiguration( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_powerfox_client: AsyncMock, +) -> None: + """Test abort when setting up duplicate entry on reconfiguration.""" + # Add two config entries + mock_config_entry.add_to_hass(hass) + mock_config_entry_2 = MockConfigEntry( + domain=DOMAIN, + data={CONF_EMAIL: "new@powerfox.test", CONF_PASSWORD: "new-password"}, + ) + mock_config_entry_2.add_to_hass(hass) + assert len(hass.config_entries.async_entries()) == 2 + + # Reconfigure the second entry + result = await mock_config_entry_2.start_reconfigure_flow(hass) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_EMAIL: "test@powerfox.test", CONF_PASSWORD: "test-password"}, + ) + + assert result.get("type") is FlowResultType.ABORT + assert result.get("reason") == "already_configured" + + @pytest.mark.parametrize( ("exception", "error"), [ @@ -216,3 +242,82 @@ async def test_step_reauth_exceptions( assert len(hass.config_entries.async_entries()) == 1 assert mock_config_entry.data[CONF_PASSWORD] == "new-password" + + +async def test_reconfigure( + hass: HomeAssistant, + mock_powerfox_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfiguration of existing entry.""" + mock_config_entry.add_to_hass(hass) + result = await mock_config_entry.start_reconfigure_flow(hass) + + assert result.get("type") is FlowResultType.FORM + assert result.get("step_id") == "reconfigure" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_EMAIL: "new-email@powerfox.test", + CONF_PASSWORD: "new-password", + }, + ) + + assert result.get("type") is FlowResultType.ABORT + assert result.get("reason") == "reconfigure_successful" + + assert len(hass.config_entries.async_entries()) == 1 + assert mock_config_entry.data[CONF_EMAIL] == "new-email@powerfox.test" + assert mock_config_entry.data[CONF_PASSWORD] == "new-password" + + +@pytest.mark.parametrize( + ("exception", "error"), + [ + (PowerfoxConnectionError, "cannot_connect"), + (PowerfoxAuthenticationError, "invalid_auth"), + ], +) +async def test_reconfigure_exceptions( + hass: HomeAssistant, + mock_powerfox_client: AsyncMock, + mock_config_entry: MockConfigEntry, + exception: Exception, + error: str, +) -> None: + """Test exceptions during reconfiguration flow.""" + mock_powerfox_client.all_devices.side_effect = exception + mock_config_entry.add_to_hass(hass) + result = await mock_config_entry.start_reconfigure_flow(hass) + + assert result.get("type") is FlowResultType.FORM + assert result.get("step_id") == "reconfigure" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_EMAIL: "new-email@powerfox.test", + CONF_PASSWORD: "new-password", + }, + ) + assert result.get("type") is FlowResultType.FORM + assert result.get("errors") == {"base": error} + + # Recover from error + mock_powerfox_client.all_devices.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={ + CONF_EMAIL: "new-email@powerfox.test", + CONF_PASSWORD: "new-password", + }, + ) + + assert result.get("type") is FlowResultType.ABORT + assert result.get("reason") == "reconfigure_successful" + + assert len(hass.config_entries.async_entries()) == 1 + assert mock_config_entry.data[CONF_EMAIL] == "new-email@powerfox.test" + assert mock_config_entry.data[CONF_PASSWORD] == "new-password"