diff --git a/homeassistant/components/trmnl/config_flow.py b/homeassistant/components/trmnl/config_flow.py index 259742a7969..d3ec0d1ca87 100644 --- a/homeassistant/components/trmnl/config_flow.py +++ b/homeassistant/components/trmnl/config_flow.py @@ -9,7 +9,12 @@ from trmnl import TRMNLClient from trmnl.exceptions import TRMNLAuthenticationError, TRMNLError import voluptuous as vol -from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult +from homeassistant.config_entries import ( + SOURCE_REAUTH, + SOURCE_RECONFIGURE, + ConfigFlow, + ConfigFlowResult, +) from homeassistant.const import CONF_API_KEY from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -24,7 +29,7 @@ class TRMNLConfigFlow(ConfigFlow, domain=DOMAIN): async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> ConfigFlowResult: - """Handle a flow initialized by the user or reauth.""" + """Handle a flow initialized by the user, reauth, or reconfigure.""" errors: dict[str, str] = {} if user_input: session = async_get_clientsession(self.hass) @@ -46,6 +51,12 @@ class TRMNLConfigFlow(ConfigFlow, domain=DOMAIN): self._get_reauth_entry(), data_updates={CONF_API_KEY: user_input[CONF_API_KEY]}, ) + if self.source == SOURCE_RECONFIGURE: + self._abort_if_unique_id_mismatch() + return self.async_update_reload_and_abort( + self._get_reconfigure_entry(), + data_updates={CONF_API_KEY: user_input[CONF_API_KEY]}, + ) self._abort_if_unique_id_configured() return self.async_create_entry( title=user.name, @@ -62,3 +73,9 @@ class TRMNLConfigFlow(ConfigFlow, domain=DOMAIN): ) -> ConfigFlowResult: """Perform reauth upon an API authentication error.""" return await self.async_step_user() + + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle reconfiguration.""" + return await self.async_step_user() diff --git a/homeassistant/components/trmnl/quality_scale.yaml b/homeassistant/components/trmnl/quality_scale.yaml index 8643a185146..29883927d6b 100644 --- a/homeassistant/components/trmnl/quality_scale.yaml +++ b/homeassistant/components/trmnl/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: There are no repairable issues diff --git a/homeassistant/components/trmnl/strings.json b/homeassistant/components/trmnl/strings.json index 4b35b6faec3..ea278acacc2 100644 --- a/homeassistant/components/trmnl/strings.json +++ b/homeassistant/components/trmnl/strings.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]", "unique_id_mismatch": "The API key belongs to a different account. Please use the API key for the original account." }, "error": { diff --git a/tests/components/trmnl/test_config_flow.py b/tests/components/trmnl/test_config_flow.py index 0609c375f45..f5aba709c16 100644 --- a/tests/components/trmnl/test_config_flow.py +++ b/tests/components/trmnl/test_config_flow.py @@ -92,9 +92,9 @@ async def test_duplicate_entry( assert result["reason"] == "already_configured" +@pytest.mark.usefixtures("mock_trmnl_client", "mock_setup_entry") async def test_reauth_flow( hass: HomeAssistant, - mock_trmnl_client: AsyncMock, mock_config_entry: MockConfigEntry, ) -> None: """Test the reauth flow.""" @@ -124,6 +124,7 @@ async def test_reauth_flow( async def test_reauth_flow_errors( hass: HomeAssistant, mock_trmnl_client: AsyncMock, + mock_setup_entry: AsyncMock, mock_config_entry: MockConfigEntry, exception: type[Exception], error: str, @@ -152,6 +153,7 @@ async def test_reauth_flow_errors( assert result["reason"] == "reauth_successful" +@pytest.mark.usefixtures("mock_setup_entry") async def test_reauth_flow_wrong_account( hass: HomeAssistant, mock_trmnl_client: AsyncMock, @@ -170,3 +172,85 @@ async def test_reauth_flow_wrong_account( assert result["type"] is FlowResultType.ABORT assert result["reason"] == "unique_id_mismatch" + + +@pytest.mark.usefixtures("mock_trmnl_client", "mock_setup_entry") +async def test_reconfigure_flow( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the reconfigure 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"] == "user" + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_API_KEY: "user_bbbbbbbbbb"} + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert mock_config_entry.data == {CONF_API_KEY: "user_bbbbbbbbbb"} + + +@pytest.mark.parametrize( + ("exception", "error"), + [ + (TRMNLAuthenticationError, "invalid_auth"), + (TRMNLError, "cannot_connect"), + (Exception, "unknown"), + ], +) +async def test_reconfigure_flow_errors( + hass: HomeAssistant, + mock_trmnl_client: AsyncMock, + mock_setup_entry: AsyncMock, + mock_config_entry: MockConfigEntry, + exception: type[Exception], + error: str, +) -> None: + """Test reconfigure flow error handling.""" + mock_config_entry.add_to_hass(hass) + + result = await mock_config_entry.start_reconfigure_flow(hass) + + mock_trmnl_client.get_me.side_effect = exception + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_API_KEY: "user_bbbbbbbbbb"} + ) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": error} + + mock_trmnl_client.get_me.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_API_KEY: "user_bbbbbbbbbb"} + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + +@pytest.mark.usefixtures("mock_setup_entry") +async def test_reconfigure_flow_wrong_account( + hass: HomeAssistant, + mock_trmnl_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfigure aborts when the API key belongs to a different account.""" + mock_config_entry.add_to_hass(hass) + + result = await mock_config_entry.start_reconfigure_flow(hass) + + mock_trmnl_client.get_me.return_value.identifier = 99999 + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_API_KEY: "user_cccccccccc"} + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "unique_id_mismatch"