Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions homeassistant/components/duco/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,38 @@ async def async_step_discovery_confirm(
description_placeholders={"name": self._box_name},
)

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the integration."""
errors: dict[str, str] = {}
reconfigure_entry = self._get_reconfigure_entry()

if user_input is not None:
try:
box_name, mac = await self._validate_input(user_input[CONF_HOST])
except DucoConnectionError:
errors["base"] = "cannot_connect"
except DucoError:
_LOGGER.exception("Unexpected error connecting to Duco box")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(format_mac(mac))
self._abort_if_unique_id_mismatch()
return self.async_update_reload_and_abort(
Comment thread
ronaldvdmeer marked this conversation as resolved.
reconfigure_entry,
title=box_name,
data_updates={CONF_HOST: user_input[CONF_HOST]},
)

return self.async_show_form(
step_id="reconfigure",
data_schema=self.add_suggested_values_to_schema(
STEP_USER_SCHEMA, reconfigure_entry.data
),
errors=errors,
)

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/duco/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ rules:
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
reconfiguration-flow: done
repair-issues:
status: exempt
comment: >-
Expand Down
10 changes: 10 additions & 0 deletions homeassistant/components/duco/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unique_id_mismatch": "The device you entered belongs to a different Duco box.",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
Expand All @@ -14,6 +16,14 @@
"discovery_confirm": {
"description": "Do you want to set up {name}?"
},
"reconfigure": {
"data": {
"host": "[%key:common::config_flow::data::host%]"
},
"data_description": {
"host": "[%key:component::duco::config::step::user::data_description::host%]"
}
},
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]"
Expand Down
107 changes: 107 additions & 0 deletions tests/components/duco/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from unittest.mock import AsyncMock

from duco.exceptions import DucoConnectionError, DucoError
from duco.models import LanInfo
import pytest

from homeassistant.components.duco.const import DOMAIN
Expand Down Expand Up @@ -206,3 +207,109 @@ async def test_zeroconf_discovery_exceptions(

assert result["type"] is FlowResultType.ABORT
assert result["reason"] == expected_reason


@pytest.mark.usefixtures("mock_setup_entry")
async def test_reconfigure_flow_success(
Comment thread
ronaldvdmeer marked this conversation as resolved.
hass: HomeAssistant,
mock_duco_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test a successful reconfigure flow updates host and reloads."""
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"

mock_duco_client.async_get_board_info.side_effect = DucoConnectionError(
"Connection refused"
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "192.168.1.50"}
)

assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
assert result["errors"] == {"base": "cannot_connect"}

mock_duco_client.async_get_board_info.side_effect = None
new_host = "192.168.1.200"
Comment thread
ronaldvdmeer marked this conversation as resolved.
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: new_host}
)

assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert mock_config_entry.data[CONF_HOST] == new_host

Comment thread
ronaldvdmeer marked this conversation as resolved.
Comment thread
ronaldvdmeer marked this conversation as resolved.
Comment thread
ronaldvdmeer marked this conversation as resolved.

@pytest.mark.usefixtures("mock_setup_entry")
async def test_reconfigure_flow_wrong_device(
hass: HomeAssistant,
mock_duco_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test reconfigure flow aborts when pointing to a different device."""
mock_config_entry.add_to_hass(hass)

result = await mock_config_entry.start_reconfigure_flow(hass)

# Simulate a different MAC returned by the new host
different_mac = "11:22:33:44:55:66"
mock_duco_client.async_get_lan_info.return_value = LanInfo(
mode="WIFI_CLIENT",
ip="192.168.1.200",
net_mask="255.255.255.0",
default_gateway="192.168.1.1",
dns="8.8.8.8",
mac=different_mac,
host_name="duco-other",
rssi_wifi=-60,
)

result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "192.168.1.200"}
)

assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "unique_id_mismatch"


@pytest.mark.usefixtures("mock_setup_entry")
@pytest.mark.parametrize(
("exception", "expected_error"),
[
(DucoConnectionError("Connection refused"), "cannot_connect"),
(DucoError("Unexpected error"), "unknown"),
],
)
async def test_reconfigure_flow_error(
hass: HomeAssistant,
mock_duco_client: AsyncMock,
mock_config_entry: MockConfigEntry,
exception: Exception,
expected_error: str,
) -> None:
"""Test reconfigure flow shows error on connection failure."""
mock_config_entry.add_to_hass(hass)

result = await mock_config_entry.start_reconfigure_flow(hass)

mock_duco_client.async_get_board_info.side_effect = exception
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "192.168.1.200"}
)

assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
assert result["errors"] == {"base": expected_error}

mock_duco_client.async_get_board_info.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_HOST: "192.168.1.200"}
)

assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
Comment thread
ronaldvdmeer marked this conversation as resolved.
Comment thread
ronaldvdmeer marked this conversation as resolved.