diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py index e2832554a22..eaaa127c471 100644 --- a/homeassistant/components/elgato/config_flow.py +++ b/homeassistant/components/elgato/config_flow.py @@ -71,6 +71,43 @@ class ElgatoFlowHandler(ConfigFlow, domain=DOMAIN): """Handle a flow initiated by zeroconf.""" return self._async_create_entry() + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle reconfiguration of an existing Elgato device.""" + errors: dict[str, str] = {} + + if user_input is not None: + elgato = Elgato( + host=user_input[CONF_HOST], + session=async_get_clientsession(self.hass), + ) + + try: + info = await elgato.info() + except ElgatoError: + errors["base"] = "cannot_connect" + else: + await self.async_set_unique_id(info.serial_number) + self._abort_if_unique_id_mismatch(reason="different_device") + return self.async_update_reload_and_abort( + self._get_reconfigure_entry(), + data_updates={CONF_HOST: user_input[CONF_HOST]}, + ) + + return self.async_show_form( + step_id="reconfigure", + data_schema=vol.Schema( + { + vol.Required( + CONF_HOST, + default=self._get_reconfigure_entry().data[CONF_HOST], + ): str, + } + ), + errors=errors, + ) + async def async_step_dhcp( self, discovery_info: DhcpServiceInfo ) -> ConfigFlowResult: diff --git a/homeassistant/components/elgato/quality_scale.yaml b/homeassistant/components/elgato/quality_scale.yaml index 4649f4ad134..4bfd143989f 100644 --- a/homeassistant/components/elgato/quality_scale.yaml +++ b/homeassistant/components/elgato/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: | diff --git a/homeassistant/components/elgato/strings.json b/homeassistant/components/elgato/strings.json index ae8d5abf962..dcfeb23d9ac 100644 --- a/homeassistant/components/elgato/strings.json +++ b/homeassistant/components/elgato/strings.json @@ -3,13 +3,23 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" + "different_device": "The configured Elgato device is not the same as the one at this address.", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "flow_title": "{serial_number}", "step": { + "reconfigure": { + "data": { + "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "[%key:component::elgato::config::step::user::data_description::host%]" + } + }, "user": { "data": { "host": "[%key:common::config_flow::data::host%]" diff --git a/tests/components/elgato/test_config_flow.py b/tests/components/elgato/test_config_flow.py index e3ed2c10818..6f05df22ae5 100644 --- a/tests/components/elgato/test_config_flow.py +++ b/tests/components/elgato/test_config_flow.py @@ -3,7 +3,7 @@ from ipaddress import ip_address from unittest.mock import AsyncMock, MagicMock -from elgato import ElgatoConnectionError +from elgato import ElgatoConnectionError, ElgatoError import pytest from homeassistant.components.elgato.const import DOMAIN @@ -331,3 +331,76 @@ async def test_dhcp_discovery_no_match( assert result["type"] is FlowResultType.ABORT assert result["reason"] == "no_devices_found" assert mock_config_entry.data[CONF_HOST] == "127.0.0.1" + + +@pytest.mark.usefixtures("mock_elgato") +async def test_reconfigure_flow( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfiguring an existing Elgato device.""" + 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"], + user_input={CONF_HOST: "127.0.0.42"}, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + assert mock_config_entry.data[CONF_HOST] == "127.0.0.42" + + +async def test_reconfigure_flow_cannot_connect( + hass: HomeAssistant, + mock_elgato: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfigure flow recovers from a connection error.""" + mock_config_entry.add_to_hass(hass) + + result = await mock_config_entry.start_reconfigure_flow(hass) + + mock_elgato.info.side_effect = ElgatoError + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_HOST: "127.0.0.42"}, + ) + + assert result["type"] is FlowResultType.FORM + assert result["step_id"] == "reconfigure" + assert result["errors"] == {"base": "cannot_connect"} + + mock_elgato.info.side_effect = None + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_HOST: "127.0.0.42"}, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + +async def test_reconfigure_flow_different_device( + hass: HomeAssistant, + mock_elgato: MagicMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfigure aborts when the device at the new host has a different serial.""" + mock_config_entry.add_to_hass(hass) + + result = await mock_config_entry.start_reconfigure_flow(hass) + + mock_elgato.info.return_value.serial_number = "DIFFERENT_SERIAL" + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + user_input={CONF_HOST: "127.0.0.42"}, + ) + + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "different_device" + assert mock_config_entry.data[CONF_HOST] == "127.0.0.1"