diff --git a/homeassistant/components/data_grand_lyon/config_flow.py b/homeassistant/components/data_grand_lyon/config_flow.py index 757e36cbe3f9..7bcabe8656d6 100644 --- a/homeassistant/components/data_grand_lyon/config_flow.py +++ b/homeassistant/components/data_grand_lyon/config_flow.py @@ -30,6 +30,12 @@ STEP_USER_DATA_SCHEMA = vol.Schema( } ) +STEP_RECONFIGURE_SCHEMA = vol.Schema( + { + vol.Required(CONF_PASSWORD): str, + } +) + STEP_STOP_DATA_SCHEMA = vol.Schema( { vol.Required(CONF_LINE): str, @@ -103,6 +109,34 @@ class DataGrandLyonConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle reconfiguration of credentials.""" + errors: dict[str, str] = {} + reconfigure_entry = self._get_reconfigure_entry() + + if user_input is not None: + creds = { + CONF_USERNAME: reconfigure_entry.data.get(CONF_USERNAME), + CONF_PASSWORD: user_input[CONF_PASSWORD], + } + if error := await self._test_connection(creds): + errors["base"] = error + else: + return self.async_update_reload_and_abort( + reconfigure_entry, data_updates=user_input + ) + + return self.async_show_form( + step_id="reconfigure", + data_schema=self.add_suggested_values_to_schema( + STEP_RECONFIGURE_SCHEMA, + user_input or reconfigure_entry.data, + ), + errors=errors, + ) + async def _test_connection(self, user_input: dict[str, Any]) -> str | None: """Test connectivity by making a dummy API call. diff --git a/homeassistant/components/data_grand_lyon/quality_scale.yaml b/homeassistant/components/data_grand_lyon/quality_scale.yaml index fa9357574073..45d66ff0a036 100644 --- a/homeassistant/components/data_grand_lyon/quality_scale.yaml +++ b/homeassistant/components/data_grand_lyon/quality_scale.yaml @@ -62,7 +62,7 @@ rules: entity-translations: done exception-translations: done icon-translations: done - reconfiguration-flow: todo + reconfiguration-flow: done repair-issues: status: exempt comment: no known use cases for repair issues or flows, yet diff --git a/homeassistant/components/data_grand_lyon/strings.json b/homeassistant/components/data_grand_lyon/strings.json index 55e36d4c325e..d7d112d66ad1 100644 --- a/homeassistant/components/data_grand_lyon/strings.json +++ b/homeassistant/components/data_grand_lyon/strings.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", - "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 @@ "username": "[%key:component::data_grand_lyon::config::step::user::data_description::username%]" } }, + "reconfigure": { + "data": { + "password": "[%key:common::config_flow::data::password%]", + "username": "[%key:common::config_flow::data::username%]" + }, + "data_description": { + "password": "[%key:component::data_grand_lyon::config::step::user::data_description::password%]", + "username": "[%key:component::data_grand_lyon::config::step::user::data_description::username%]" + } + }, "user": { "data": { "password": "[%key:common::config_flow::data::password%]", diff --git a/tests/components/data_grand_lyon/test_config_flow.py b/tests/components/data_grand_lyon/test_config_flow.py index 966331281b27..ed338880651f 100644 --- a/tests/components/data_grand_lyon/test_config_flow.py +++ b/tests/components/data_grand_lyon/test_config_flow.py @@ -193,6 +193,81 @@ async def test_form_already_configured( assert result["reason"] == "already_configured" +async def test_reconfigure_flow( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_get_tcl_passages: AsyncMock, +) -> None: + """Test the reconfigure flow updates credentials and preserves subentries.""" + mock_config_entry.add_to_hass(hass) + original_subentries = dict(mock_config_entry.subentries) + + 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_PASSWORD: "new-pass"}, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert mock_config_entry.data == { + CONF_USERNAME: "user", + CONF_PASSWORD: "new-pass", + } + assert dict(mock_config_entry.subentries) == original_subentries + + +@pytest.mark.parametrize( + ("side_effect", "error"), + [ + (ClientConnectionError(), "cannot_connect"), + (ClientResponseError(None, None, status=401), "invalid_auth"), + (ClientResponseError(None, None, status=500), "cannot_connect"), + (RuntimeError("unexpected"), "unknown"), + ], + ids=["connection-error", "auth-401", "http-500", "unknown"], +) +async def test_reconfigure_flow_errors( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_get_tcl_passages: AsyncMock, + side_effect: Exception, + error: str, +) -> None: + """Test the reconfigure flow shows errors and recovers.""" + mock_config_entry.add_to_hass(hass) + + result = await mock_config_entry.start_reconfigure_flow(hass) + + mock_get_tcl_passages.side_effect = side_effect + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_PASSWORD: "new-pass"}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "reconfigure" + assert result["errors"] == {"base": error} + + mock_get_tcl_passages.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_PASSWORD: "new-pass"}, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert mock_config_entry.data == { + CONF_USERNAME: "user", + CONF_PASSWORD: "new-pass", + } + + # Stop subentry tests