Add reconfigure flow to WattWächter Plus (#175551)

This commit is contained in:
smartcircuits
2026-07-18 13:39:49 +02:00
committed by GitHub
parent 8aa43c6a14
commit 1ce5ae3c0b
4 changed files with 159 additions and 2 deletions
@@ -21,6 +21,11 @@ from homeassistant.const import (
CONF_TOKEN,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
TextSelectorType,
)
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from .const import CONF_FW_VERSION, DOMAIN
@@ -247,3 +252,42 @@ class WattwaechterConfigFlow(ConfigFlow, domain=DOMAIN):
description_placeholders={"host": reauth_entry.data[CONF_HOST]},
errors=errors,
)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the host and token."""
reconfigure_entry = self._get_reconfigure_entry()
errors: dict[str, str] = {}
if user_input is not None:
# Normalize a cleared token field to None, matching how token-less
# devices are stored everywhere else in the integration.
token = user_input.get(CONF_TOKEN) or None
errors, system_info, _ = await self._async_test_connection(
user_input[CONF_HOST], token
)
if not errors:
assert system_info is not None
await self.async_set_unique_id(system_info.get_value("esp", "esp_id"))
self._abort_if_unique_id_mismatch(reason="wrong_device")
return self.async_update_reload_and_abort(
reconfigure_entry,
data_updates={CONF_HOST: user_input[CONF_HOST], CONF_TOKEN: token},
)
schema = vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Optional(CONF_TOKEN): TextSelector(
TextSelectorConfig(type=TextSelectorType.PASSWORD)
),
}
)
return self.async_show_form(
step_id="reconfigure",
data_schema=self.add_suggested_values_to_schema(
schema, user_input or reconfigure_entry.data
),
errors=errors,
)
@@ -68,7 +68,7 @@ rules:
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
reconfiguration-flow: done
repair-issues:
status: exempt
comment: No actionable repair scenarios for this device type.
@@ -4,7 +4,8 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"wrong_device": "The re-authenticated device does not match the original WattWächter Plus device."
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"wrong_device": "The device does not match the original WattWächter Plus device."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -31,6 +32,17 @@
"description": "The API token for {host} is no longer valid. Enter a new token to reconnect.",
"title": "Re-authenticate WattWächter Plus"
},
"reconfigure": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"token": "[%key:common::config_flow::data::api_token%]"
},
"data_description": {
"host": "[%key:component::wattwaechter::config::step::user::data_description::host%]",
"token": "[%key:component::wattwaechter::config::step::auth::data_description::token%]"
},
"title": "Reconfigure WattWächter Plus"
},
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]"
@@ -479,3 +479,104 @@ async def test_reauth_flow_wrong_device(
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "wrong_device"
assert mock_config_entry.data[CONF_TOKEN] == MOCK_TOKEN
async def test_reconfigure_flow_success(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test reconfiguring the host updates the entry and keeps the token."""
result = await mock_config_entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
new_host = "192.168.1.222"
# The token field is pre-filled with the stored token and submitted as-is
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: new_host, CONF_TOKEN: MOCK_TOKEN}
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert mock_config_entry.data[CONF_HOST] == new_host
assert mock_config_entry.data[CONF_TOKEN] == MOCK_TOKEN
async def test_reconfigure_flow_clear_token(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test clearing the token field stores None instead of an empty string."""
result = await mock_config_entry.start_reconfigure_flow(hass)
assert result["step_id"] == "reconfigure"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: MOCK_HOST, CONF_TOKEN: ""}
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert mock_config_entry.data[CONF_TOKEN] is None
@pytest.mark.parametrize(
("side_effect", "expected_error"),
[
(WattwaechterAuthenticationError("Invalid token"), "invalid_auth"),
(WattwaechterConnectionError("Connection lost"), "cannot_connect"),
],
)
async def test_reconfigure_flow_errors(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
side_effect: Exception,
expected_error: str,
) -> None:
"""Test reconfigure recovers after an invalid token or connection failure."""
result = await mock_config_entry.start_reconfigure_flow(hass)
assert result["step_id"] == "reconfigure"
mock_client.system_info.side_effect = side_effect
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: MOCK_HOST}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
assert result["errors"]["base"] == expected_error
# Retry succeeds once the device is reachable again
mock_client.system_info.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: MOCK_HOST}
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
async def test_reconfigure_flow_wrong_device(
hass: HomeAssistant,
mock_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test reconfigure aborts when the host points to a different device."""
result = await mock_config_entry.start_reconfigure_flow(hass)
assert result["step_id"] == "reconfigure"
mock_client.system_info.return_value = MagicMock(
**{"get_value.return_value": "WRONG-DEVICE"}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "192.168.1.222"}
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "wrong_device"
assert mock_config_entry.data[CONF_HOST] == MOCK_HOST