diff --git a/homeassistant/components/uptimerobot/config_flow.py b/homeassistant/components/uptimerobot/config_flow.py index 5fc165c0f27..ccbf6c39655 100644 --- a/homeassistant/components/uptimerobot/config_flow.py +++ b/homeassistant/components/uptimerobot/config_flow.py @@ -116,3 +116,30 @@ class UptimeRobotConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_show_form( step_id="reauth_confirm", 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 device.""" + reconfigure_entry = self._get_reconfigure_entry() + if not user_input: + return self.async_show_form( + step_id="reconfigure", + data_schema=STEP_USER_DATA_SCHEMA, + ) + + self._async_abort_entries_match( + {CONF_API_KEY: reconfigure_entry.data[CONF_API_KEY]} + ) + + errors, account = await self._validate_input(user_input) + if account: + await self.async_set_unique_id(str(account.user_id)) + self._abort_if_unique_id_configured() + 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/uptimerobot/quality_scale.yaml b/homeassistant/components/uptimerobot/quality_scale.yaml index 2152f572853..01da4dc5166 100644 --- a/homeassistant/components/uptimerobot/quality_scale.yaml +++ b/homeassistant/components/uptimerobot/quality_scale.yaml @@ -68,9 +68,7 @@ rules: entity-translations: done exception-translations: done icon-translations: done - reconfiguration-flow: - status: todo - comment: handle API key change/update + reconfiguration-flow: done repair-issues: status: exempt comment: no known use cases for repair issues or flows, yet diff --git a/homeassistant/components/uptimerobot/strings.json b/homeassistant/components/uptimerobot/strings.json index ffee6769c69..f912b6dd993 100644 --- a/homeassistant/components/uptimerobot/strings.json +++ b/homeassistant/components/uptimerobot/strings.json @@ -17,6 +17,14 @@ "data_description": { "api_key": "[%key:component::uptimerobot::config::step::user::data_description::api_key%]" } + }, + "reconfigure": { + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]" + }, + "data_description": { + "api_key": "[%key:component::uptimerobot::config::step::user::data_description::api_key%]" + } } }, "error": { @@ -30,6 +38,7 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "reauth_failed_existing": "Could not update the config entry, please remove the integration and set it up again.", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]", "unknown": "[%key:common::config_flow::error::unknown%]" } }, diff --git a/tests/components/uptimerobot/test_config_flow.py b/tests/components/uptimerobot/test_config_flow.py index c7ae6a5d772..621d9cc27c3 100644 --- a/tests/components/uptimerobot/test_config_flow.py +++ b/tests/components/uptimerobot/test_config_flow.py @@ -3,7 +3,11 @@ from unittest.mock import patch import pytest -from pyuptimerobot import UptimeRobotAuthenticationException, UptimeRobotException +from pyuptimerobot import ( + UptimeRobotApiResponse, + UptimeRobotAuthenticationException, + UptimeRobotException, +) from homeassistant import config_entries from homeassistant.components.uptimerobot.const import DOMAIN @@ -35,7 +39,7 @@ async def test_user(hass: HomeAssistant) -> None: with ( patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ), patch( @@ -66,7 +70,7 @@ async def test_user_key_read_only(hass: HomeAssistant) -> None: assert result["errors"] is None with patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ): result2 = await hass.config_entries.flow.async_configure( @@ -94,7 +98,7 @@ async def test_exception_thrown(hass: HomeAssistant, exception, error_key) -> No ) with patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", side_effect=exception, ): result2 = await hass.config_entries.flow.async_configure( @@ -113,7 +117,7 @@ async def test_api_error(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) ) with patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR), ): result2 = await hass.config_entries.flow.async_configure( @@ -140,7 +144,7 @@ async def test_user_unique_id_already_exists( with ( patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ), patch( @@ -174,7 +178,7 @@ async def test_reauthentication( with ( patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ), patch( @@ -207,7 +211,7 @@ async def test_reauthentication_failure( with ( patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR), ), patch( @@ -243,7 +247,7 @@ async def test_reauthentication_failure_no_existing_entry( with ( patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ), patch( @@ -276,7 +280,7 @@ async def test_reauthentication_failure_account_not_matching( with ( patch( - "pyuptimerobot.UptimeRobot.async_get_account_details", + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", return_value=mock_uptimerobot_api_response( key=MockApiResponseKey.ACCOUNT, data={**MOCK_UPTIMEROBOT_ACCOUNT, "user_id": 1234567891}, @@ -296,3 +300,179 @@ async def test_reauthentication_failure_account_not_matching( assert result2["step_id"] == "reauth_confirm" assert result2["type"] is FlowResultType.FORM assert result2["errors"]["base"] == "reauth_failed_matching_account" + + +async def test_reconfigure_successful( + hass: HomeAssistant, +) -> None: + """Test that the entry can be reconfigured.""" + config_entry = MockConfigEntry( + **{**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA, "unique_id": None} + ) + config_entry.add_to_hass(hass) + + result = await config_entry.start_reconfigure_flow(hass) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] is None + assert result["step_id"] == "reconfigure" + + new_key = "u0242ac120003-new" + + with ( + patch( + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, + ), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_API_KEY: new_key}, + ) + + assert result2["type"] is FlowResultType.ABORT + assert result2["reason"] == "reconfigure_successful" + + # changed entry + assert config_entry.data[CONF_API_KEY] == new_key + + +async def test_reconfigure_failed( + hass: HomeAssistant, +) -> None: + """Test that the entry reconfigure fails with a wrong key.""" + config_entry = MockConfigEntry( + **{**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA, "unique_id": None} + ) + config_entry.add_to_hass(hass) + + result = await config_entry.start_reconfigure_flow(hass) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] is None + assert result["step_id"] == "reconfigure" + + wrong_key = "u0242ac120003-wrong" + + with ( + patch( + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", + side_effect=UptimeRobotAuthenticationException, + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, + ), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_API_KEY: wrong_key}, + ) + + assert result2["type"] is FlowResultType.FORM + assert result2["errors"]["base"] == "invalid_api_key" + + new_key = "u0242ac120003-new" + + with ( + patch( + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, + ), + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + user_input={CONF_API_KEY: new_key}, + ) + + assert result3["type"] is FlowResultType.ABORT + assert result3["reason"] == "reconfigure_successful" + + # changed entry + assert config_entry.data[CONF_API_KEY] == new_key + + +async def test_reconfigure_with_key_present( + hass: HomeAssistant, +) -> None: + """Test that the entry reconfigure fails with a key from another entry.""" + config_entry = MockConfigEntry( + **{**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA, "unique_id": None} + ) + config_entry.add_to_hass(hass) + + api_key_2 = "u0242ac120003-2" + email_2 = "test2@test.test" + user_id_2 = "abcdefghil" + data2 = { + "domain": DOMAIN, + "title": email_2, + "data": {"platform": DOMAIN, "api_key": api_key_2}, + "unique_id": user_id_2, + "source": config_entries.SOURCE_USER, + } + config_entry_2 = MockConfigEntry(**{**data2, "unique_id": None}) + config_entry_2.add_to_hass(hass) + + result = await config_entry.start_reconfigure_flow(hass) + + assert result["type"] is FlowResultType.FORM + assert result["errors"] is None + assert result["step_id"] == "reconfigure" + + with ( + patch( + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", + return_value=UptimeRobotApiResponse.from_dict( + { + "stat": "ok", + "email": email_2, + "user_id": user_id_2, + "up_monitors": 1, + } + ), + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, + ), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_API_KEY: api_key_2}, + ) + + assert result2["type"] is FlowResultType.FORM + assert result2["errors"] == {} + assert result2["step_id"] == "reconfigure" + + new_key = "u0242ac120003-new" + + with ( + patch( + "homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details", + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), + ), + patch( + "homeassistant.components.uptimerobot.async_setup_entry", + return_value=True, + ), + ): + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + user_input={CONF_API_KEY: new_key}, + ) + + assert result3["type"] is FlowResultType.ABORT + assert result3["reason"] == "reconfigure_successful" + + # changed entry + assert config_entry.data[CONF_API_KEY] == new_key