From 9882fe0eda394b76318da1adf3eab5ea8b2ae8b1 Mon Sep 17 00:00:00 2001 From: Glenn de Haan Date: Tue, 27 Jan 2026 18:50:37 +0100 Subject: [PATCH] Add HDFury reconfiguration (#161690) --- .../components/hdfury/config_flow.py | 37 +++++ .../components/hdfury/quality_scale.yaml | 2 +- homeassistant/components/hdfury/strings.json | 13 +- tests/components/hdfury/test_config_flow.py | 132 ++++++++++++++++++ 4 files changed, 182 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/hdfury/config_flow.py b/homeassistant/components/hdfury/config_flow.py index 01852388849c..49d858d5c966 100644 --- a/homeassistant/components/hdfury/config_flow.py +++ b/homeassistant/components/hdfury/config_flow.py @@ -83,6 +83,43 @@ class HDFuryConfigFlow(ConfigFlow, domain=DOMAIN): errors=errors, ) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Handle reconfiguration.""" + errors: dict[str, str] = {} + reconfigure_entry = self._get_reconfigure_entry() + + if user_input is not None: + host = user_input[CONF_HOST] + + serial = await self._validate_connection(host) + if serial is not None: + await self.async_set_unique_id(serial) + self._abort_if_unique_id_mismatch(reason="incorrect_device") + return self.async_update_reload_and_abort( + self._get_reconfigure_entry(), + data_updates=user_input, + ) + + errors["base"] = "cannot_connect" + + return self.async_show_form( + step_id="reconfigure", + data_schema=vol.Schema( + { + vol.Required( + CONF_HOST, + default=reconfigure_entry.data.get(CONF_HOST), + ): str + } + ), + description_placeholders={ + "title": reconfigure_entry.title, + }, + errors=errors, + ) + async def _validate_connection(self, host: str) -> str | None: """Try to fetch serial number to confirm it's a valid HDFury device.""" diff --git a/homeassistant/components/hdfury/quality_scale.yaml b/homeassistant/components/hdfury/quality_scale.yaml index ab135a9fd51b..99ec79d812a6 100644 --- a/homeassistant/components/hdfury/quality_scale.yaml +++ b/homeassistant/components/hdfury/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: todo stale-devices: status: exempt diff --git a/homeassistant/components/hdfury/strings.json b/homeassistant/components/hdfury/strings.json index 9bd2ee20bac9..02b0ca6aa87e 100644 --- a/homeassistant/components/hdfury/strings.json +++ b/homeassistant/components/hdfury/strings.json @@ -2,7 +2,9 @@ "config": { "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "incorrect_device": "The configured device is not the same found on this IP address.", + "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" }, "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" @@ -12,6 +14,15 @@ "discovery_confirm": { "description": "Do you want to set up {host}?" }, + "reconfigure": { + "data": { + "host": "[%key:common::config_flow::data::host%]" + }, + "data_description": { + "host": "[%key:component::hdfury::config::step::user::data_description::host%]" + }, + "description": "Update configuration for {title}." + }, "user": { "data": { "host": "[%key:common::config_flow::data::host%]" diff --git a/tests/components/hdfury/test_config_flow.py b/tests/components/hdfury/test_config_flow.py index 5a2567bc80be..a866f27851ef 100644 --- a/tests/components/hdfury/test_config_flow.py +++ b/tests/components/hdfury/test_config_flow.py @@ -170,3 +170,135 @@ async def test_zeroconf_flow_abort_duplicate( ) assert result["type"] is FlowResultType.ABORT assert result["reason"] == "already_configured" + + +async def test_reconfigure_flow( + hass: HomeAssistant, + mock_hdfury_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfiguration.""" + 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" + assert result["errors"] == {} + + # Original entry + assert mock_config_entry.data[CONF_HOST] == "192.168.1.123" + assert mock_config_entry.unique_id == "000123456789" + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "192.168.1.124", + }, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + # Changed entry + assert mock_config_entry.data[CONF_HOST] == "192.168.1.124" + assert mock_config_entry.unique_id == "000123456789" + + +async def test_reconfigure_flow_no_change( + hass: HomeAssistant, + mock_hdfury_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfiguration without changing values.""" + 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" + assert result["errors"] == {} + + # Original entry + assert mock_config_entry.data[CONF_HOST] == "192.168.1.123" + assert mock_config_entry.unique_id == "000123456789" + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "192.168.1.123", + }, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + # Changed entry + assert mock_config_entry.data[CONF_HOST] == "192.168.1.123" + assert mock_config_entry.unique_id == "000123456789" + + +async def test_reconfigure_flow_abort_incorrect_device( + hass: HomeAssistant, + mock_hdfury_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test ip of other device with different serial.""" + 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" + assert result["errors"] == {} + + # Simulate different serial number, as if user entered wrong IP + mock_hdfury_client.get_board.return_value = { + "hostname": "VRROOM-21", + "ipaddress": "192.168.1.124", + "serial": "000987654321", + "pcbv": "3", + "version": "FW: 0.61", + } + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "192.168.1.124", + }, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "incorrect_device" + + # Entry should still be original entry + assert mock_config_entry.data[CONF_HOST] == "192.168.1.123" + assert mock_config_entry.unique_id == "000123456789" + + +async def test_reconfigure_flow_cannot_connect( + hass: HomeAssistant, + mock_hdfury_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test reconfiguration fails with cannot connect.""" + 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" + assert result["errors"] == {} + + # Simulate a connection error by raising a HDFuryError + mock_hdfury_client.get_board.side_effect = HDFuryError() + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "192.168.1.124", + }, + ) + assert result["type"] is FlowResultType.FORM + assert result["errors"] == {"base": "cannot_connect"} + assert result["data_schema"]({}) == {CONF_HOST: "192.168.1.123"} + + # Attempt with valid IP should work + mock_hdfury_client.get_board.side_effect = None + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + CONF_HOST: "192.168.1.124", + }, + ) + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "reconfigure_successful" + + # Changed entry + assert mock_config_entry.data[CONF_HOST] == "192.168.1.124" + assert mock_config_entry.unique_id == "000123456789"