diff --git a/homeassistant/components/huum/config_flow.py b/homeassistant/components/huum/config_flow.py index c5cdc18107a1..d6c93e9dedb2 100644 --- a/homeassistant/components/huum/config_flow.py +++ b/homeassistant/components/huum/config_flow.py @@ -59,6 +59,43 @@ class HuumConfigFlow(ConfigFlow, domain=DOMAIN): step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors ) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle reconfiguration of the integration.""" + errors: dict[str, str] = {} + reconfigure_entry = self._get_reconfigure_entry() + + if user_input is not None: + self._async_abort_entries_match({CONF_USERNAME: user_input[CONF_USERNAME]}) + try: + huum = Huum( + user_input[CONF_USERNAME], + user_input[CONF_PASSWORD], + session=async_get_clientsession(self.hass), + ) + await huum.status() + except Forbidden, NotAuthenticated: + errors["base"] = "invalid_auth" + except Exception: + _LOGGER.exception("Unknown error") + errors["base"] = "unknown" + else: + return self.async_update_reload_and_abort( + reconfigure_entry, + title=user_input[CONF_USERNAME], + data_updates=user_input, + ) + + return self.async_show_form( + step_id="reconfigure", + data_schema=self.add_suggested_values_to_schema( + STEP_USER_DATA_SCHEMA, + {CONF_USERNAME: reconfigure_entry.data[CONF_USERNAME]}, + ), + errors=errors, + ) + async def async_step_reauth( self, entry_data: Mapping[str, Any] ) -> ConfigFlowResult: diff --git a/homeassistant/components/huum/quality_scale.yaml b/homeassistant/components/huum/quality_scale.yaml index 72fc2db3428b..6422498628a0 100644 --- a/homeassistant/components/huum/quality_scale.yaml +++ b/homeassistant/components/huum/quality_scale.yaml @@ -64,7 +64,7 @@ rules: entity-translations: done exception-translations: done icon-translations: done - reconfiguration-flow: todo + reconfiguration-flow: done repair-issues: status: exempt comment: Integration has no repair scenarios. diff --git a/homeassistant/components/huum/strings.json b/homeassistant/components/huum/strings.json index 8b50fcd5eeeb..e7c597838071 100644 --- a/homeassistant/components/huum/strings.json +++ b/homeassistant/components/huum/strings.json @@ -2,7 +2,8 @@ "config": { "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%]" }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", @@ -20,6 +21,16 @@ "description": "The authentication for {username} is no longer valid. Please enter the current password.", "title": "[%key:common::config_flow::title::reauth%]" }, + "reconfigure": { + "data": { + "password": "[%key:common::config_flow::data::password%]", + "username": "[%key:common::config_flow::data::username%]" + }, + "data_description": { + "password": "[%key:component::huum::config::step::user::data_description::password%]", + "username": "[%key:component::huum::config::step::user::data_description::username%]" + } + }, "user": { "data": { "password": "[%key:common::config_flow::data::password%]", diff --git a/tests/components/huum/test_config_flow.py b/tests/components/huum/test_config_flow.py index 511fe89636b4..1de9494ad8bd 100644 --- a/tests/components/huum/test_config_flow.py +++ b/tests/components/huum/test_config_flow.py @@ -183,3 +183,115 @@ async def test_reauth_errors( assert result["reason"] == "reauth_successful" assert mock_config_entry.data[CONF_USERNAME] == TEST_USERNAME assert mock_config_entry.data[CONF_PASSWORD] == "new_password" + + +@pytest.mark.usefixtures("mock_huum_client", "mock_setup_entry") +async def test_reconfigure_flow( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfiguration flow.""" + mock_config_entry.add_to_hass(hass) + result = await mock_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_USERNAME: "new@sauna.org", + CONF_PASSWORD: "new_password", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert mock_config_entry.title == "new@sauna.org" + assert mock_config_entry.data[CONF_USERNAME] == "new@sauna.org" + assert mock_config_entry.data[CONF_PASSWORD] == "new_password" + + +@pytest.mark.usefixtures("mock_huum_client", "mock_setup_entry") +async def test_reconfigure_flow_already_configured( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfiguration flow aborts when username already configured.""" + mock_config_entry.add_to_hass(hass) + + other_entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_USERNAME: "other@sauna.org", + CONF_PASSWORD: "other_password", + }, + entry_id="OTHER_ENTRY", + ) + other_entry.add_to_hass(hass) + + result = await mock_config_entry.start_reconfigure_flow(hass) + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: "other@sauna.org", + CONF_PASSWORD: "new_password", + }, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "already_configured" + + +@pytest.mark.parametrize( + ( + "raises", + "error_base", + ), + [ + (Exception, "unknown"), + (Forbidden, "invalid_auth"), + ], +) +async def test_reconfigure_errors( + hass: HomeAssistant, + mock_huum_client: AsyncMock, + mock_setup_entry: AsyncMock, + mock_config_entry: MockConfigEntry, + raises: Exception, + error_base: str, +) -> None: + """Test reconfiguration flow handles errors and recovers.""" + mock_config_entry.add_to_hass(hass) + + result = await mock_config_entry.start_reconfigure_flow(hass) + + mock_huum_client.status.side_effect = raises + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: TEST_USERNAME, + CONF_PASSWORD: "wrong_password", + }, + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": error_base} + + # Recover with valid credentials + mock_huum_client.status.side_effect = None + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_USERNAME: TEST_USERNAME, + CONF_PASSWORD: "new_password", + }, + ) + await hass.async_block_till_done() + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert mock_config_entry.data[CONF_USERNAME] == TEST_USERNAME + assert mock_config_entry.data[CONF_PASSWORD] == "new_password"