Skip to content
Merged
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
34 changes: 29 additions & 5 deletions homeassistant/components/hotspring/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
"""Config flow for Hot Spring."""

from typing import override
from collections.abc import Mapping
from typing import Any, override

from hotspring import HotSpring, HotSpringConnectionError, HotSpringError, Spa
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import (
SOURCE_RECONFIGURE,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
Expand All @@ -20,7 +25,7 @@
)


async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> Spa:
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> Spa:
"""Validate the user input allows us to connect."""
api = HotSpring(data[CONF_HOST], session=async_get_clientsession(hass))
spa = await api.update()
Expand All @@ -36,7 +41,7 @@ class HotSpringConfigFlow(ConfigFlow, domain=DOMAIN):

@override
async def async_step_user(
self, user_input: dict[str, str] | None = None
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initiated by the user."""
errors: dict[str, str] = {}
Expand All @@ -47,6 +52,12 @@ async def async_step_user(
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(spa.info.mac_address)
if self.source == SOURCE_RECONFIGURE:
self._abort_if_unique_id_mismatch()
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data_updates={CONF_HOST: user_input[CONF_HOST]},
)
self._abort_if_unique_id_configured(
updates={CONF_HOST: user_input[CONF_HOST]}
)
Expand All @@ -57,8 +68,21 @@ async def async_step_user(
},
)

suggested_values: Mapping[str, Any] | None = user_input
Comment thread
Moustachauve marked this conversation as resolved.
if suggested_values is None and self.source == SOURCE_RECONFIGURE:
suggested_values = self._get_reconfigure_entry().data

return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
data_schema=self.add_suggested_values_to_schema(
STEP_USER_DATA_SCHEMA,
suggested_values,
),
errors=errors,
)

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the Hot Spring spa."""
return await self.async_step_user(user_input)
2 changes: 1 addition & 1 deletion homeassistant/components/hotspring/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ rules:
icon-translations:
status: exempt
comment: Entity relies on standard platform default icons.
reconfiguration-flow: todo
reconfiguration-flow: done
repair-issues:
status: exempt
comment: Integration does not raise repair issues.
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/hotspring/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"unique_id_mismatch": "The MAC address does not match the configured device. Please ensure you reconfigure against the same device."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
Expand Down
116 changes: 99 additions & 17 deletions tests/components/hotspring/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from unittest.mock import MagicMock

from hotspring import HotSpringConnectionError, HotSpringError, Spa, SpaBrand, SpaInfo
from hotspring import HotSpringConnectionError, HotSpringError, Spa
import pytest

from homeassistant.components.hotspring.const import DOMAIN
Expand All @@ -11,7 +11,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType

from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, get_schema_suggested_value


@pytest.mark.usefixtures("mock_setup_entry", "mock_hotspring")
Expand Down Expand Up @@ -97,20 +97,7 @@ async def test_form_no_mac_address(
hass: HomeAssistant, mock_hotspring: MagicMock, device_fixture: Spa
) -> None:
"""Test we show user form on missing MAC address and recover."""
valid_info = device_fixture.info
device_fixture.info = SpaInfo(
hostname="ConnectedSpa_DDEEFF",
root_topic="unknownTopic123",
sna_ready=True,
brand=SpaBrand.HOTSPRING,
brand_name="Hot Spring",
collection="Highlife",
model_name="Relay",
brand_id="1",
collection_id="1",
model_id="1",
volume=335,
)
device_fixture.info.root_topic = "unknownTopic123"
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
Expand All @@ -123,7 +110,7 @@ async def test_form_no_mac_address(
assert result["step_id"] == "user"
assert result["errors"] == {"base": "cannot_connect"}

device_fixture.info = valid_info
device_fixture.info.root_topic = "mySpaAABBCCDDEEFF"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_HOST: "192.168.1.100"}
)
Expand All @@ -132,3 +119,98 @@ async def test_form_no_mac_address(
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == {CONF_HOST: "192.168.1.100"}
assert result["result"].unique_id == "AA:BB:CC:DD:EE:FF"


@pytest.mark.usefixtures("mock_setup_entry")
async def test_full_reconfigure_flow_success(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hotspring: MagicMock,
) -> None:
"""Test the full reconfigure flow from start to finish."""
mock_config_entry.add_to_hass(hass)

result = await mock_config_entry.start_reconfigure_flow(hass)

assert result["step_id"] == "user"
assert result["type"] is FlowResultType.FORM
assert result["data_schema"] is not None
assert (
get_schema_suggested_value(result["data_schema"].schema, CONF_HOST)
== "192.168.1.100"
)

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

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

assert mock_config_entry.data[CONF_HOST] == "192.168.1.200"


async def test_full_reconfigure_flow_unique_id_mismatch(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hotspring: MagicMock,
device_fixture: Spa,
) -> None:
"""Test reconfiguration failure when the unique ID changes."""
mock_config_entry.add_to_hass(hass)
device_fixture.info.root_topic = "mySpa112233445566"

result = await mock_config_entry.start_reconfigure_flow(hass)

assert result["step_id"] == "user"
assert result["type"] is FlowResultType.FORM

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

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


@pytest.mark.usefixtures("mock_setup_entry")
async def test_full_reconfigure_flow_connection_error_and_success(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hotspring: MagicMock,
) -> None:
"""Test reconfigure flow with connection error and recovery."""
mock_config_entry.add_to_hass(hass)

result = await mock_config_entry.start_reconfigure_flow(hass)

assert result["step_id"] == "user"
assert result["type"] is FlowResultType.FORM
assert result["data_schema"] is not None
assert (
get_schema_suggested_value(result["data_schema"].schema, CONF_HOST)
== "192.168.1.100"
)

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

assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {"base": "cannot_connect"}
assert result["data_schema"] is not None
assert (
get_schema_suggested_value(result["data_schema"].schema, CONF_HOST)
== "192.168.1.200"
)

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

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