Skip to content

Commit 29f9a42

Browse files
committed
Support importing legacy YAML config
Add support for importing legacy login information from YAML config file as part of the config flow upgrade process. Co-authored-by: Claude Sonnet 4.5 [email protected]
1 parent d06c230 commit 29f9a42

File tree

4 files changed

+171
-2
lines changed

4 files changed

+171
-2
lines changed

homeassistant/components/waterfurnace/__init__.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
import logging
66

7+
import voluptuous as vol
78
from waterfurnace.waterfurnace import WaterFurnace, WFCredentialError, WFException
89

9-
from homeassistant.config_entries import ConfigEntry
10+
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
1011
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
1112
from homeassistant.core import HomeAssistant
1213
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
13-
from homeassistant.helpers import device_registry as dr
14+
from homeassistant.helpers import config_validation as cv, device_registry as dr
15+
from homeassistant.helpers.typing import ConfigType
1416

1517
from .const import DOMAIN
1618
from .coordinator import WaterFurnaceDataUpdateCoordinator
@@ -20,6 +22,31 @@
2022

2123
PLATFORMS = [Platform.SENSOR]
2224

25+
CONFIG_SCHEMA = vol.Schema(
26+
{
27+
DOMAIN: vol.Schema(
28+
{
29+
vol.Required(CONF_USERNAME): cv.string,
30+
vol.Required(CONF_PASSWORD): cv.string,
31+
}
32+
)
33+
},
34+
extra=vol.ALLOW_EXTRA,
35+
)
36+
37+
38+
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
39+
"""Set up WaterFurnace from yaml configuration."""
40+
if DOMAIN in config:
41+
hass.async_create_task(
42+
hass.config_entries.flow.async_init(
43+
DOMAIN,
44+
context={"source": SOURCE_IMPORT},
45+
data=config[DOMAIN],
46+
)
47+
)
48+
return True
49+
2350

2451
async def async_setup_entry(
2552
hass: HomeAssistant, entry: WaterFurnaceConfigEntry

homeassistant/components/waterfurnace/config_flow.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,29 @@ async def async_step_reauth_confirm(
152152
errors=errors,
153153
)
154154

155+
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
156+
"""Handle import from YAML configuration."""
157+
try:
158+
info = await validate_input(self.hass, import_data)
159+
except (CannotConnect, InvalidAuth):
160+
_LOGGER.error(
161+
"Failed to import WaterFurnace configuration from YAML. "
162+
"Please verify your credentials and set up the integration via the UI"
163+
)
164+
return self.async_abort(reason="cannot_connect")
165+
except Exception:
166+
_LOGGER.exception("Unexpected error importing WaterFurnace configuration")
167+
return self.async_abort(reason="unknown")
168+
169+
# Set unique ID based on GWID
170+
await self.async_set_unique_id(info["gwid"])
171+
self._abort_if_unique_id_configured()
172+
173+
return self.async_create_entry(
174+
title=info["title"],
175+
data=import_data,
176+
)
177+
155178

156179
class CannotConnect(HomeAssistantError):
157180
"""Error to indicate we cannot connect."""

homeassistant/components/waterfurnace/strings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
"config": {
33
"abort": {
44
"already_configured": "This device is already configured",
5+
"cannot_connect": "Failed to import configuration from YAML. Please verify your credentials and set up the integration via the UI",
56
"no_devices": "No devices found on this account",
67
"reauth_successful": "Reauthentication successful",
8+
"unknown": "Failed to import configuration from YAML due to an unexpected error. Please set up the integration via the UI",
79
"wrong_account": "The credentials provided are for a different account"
810
},
911
"error": {

tests/components/waterfurnace/test_config_flow.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,3 +409,120 @@ async def test_reauth_flow_unexpected_error(
409409

410410
assert result["type"] is FlowResultType.FORM
411411
assert result["errors"] == {"base": "cannot_connect"}
412+
413+
414+
async def test_import_flow_success(
415+
hass: HomeAssistant, mock_waterfurnace_client: Mock
416+
) -> None:
417+
"""Test successful import flow from YAML."""
418+
result = cast(
419+
dict[str, Any],
420+
await hass.config_entries.flow.async_init(
421+
DOMAIN,
422+
context={"source": config_entries.SOURCE_IMPORT},
423+
data={
424+
CONF_USERNAME: "test_user",
425+
CONF_PASSWORD: "test_password",
426+
},
427+
),
428+
)
429+
430+
assert result["type"] is FlowResultType.CREATE_ENTRY
431+
assert result["title"] == "WaterFurnace TEST_GWID_12345"
432+
assert result["data"] == {
433+
CONF_USERNAME: "test_user",
434+
CONF_PASSWORD: "test_password",
435+
}
436+
assert result["result"].unique_id == "TEST_GWID_12345"
437+
438+
439+
async def test_import_flow_already_configured(
440+
hass: HomeAssistant,
441+
mock_waterfurnace_client: Mock,
442+
mock_config_entry: MockConfigEntry,
443+
) -> None:
444+
"""Test import flow when device is already configured."""
445+
mock_config_entry.add_to_hass(hass)
446+
447+
result = cast(
448+
dict[str, Any],
449+
await hass.config_entries.flow.async_init(
450+
DOMAIN,
451+
context={"source": config_entries.SOURCE_IMPORT},
452+
data={
453+
CONF_USERNAME: "test_user",
454+
CONF_PASSWORD: "test_password",
455+
},
456+
),
457+
)
458+
459+
assert result["type"] is FlowResultType.ABORT
460+
assert result["reason"] == "already_configured"
461+
462+
463+
async def test_import_flow_cannot_connect(
464+
hass: HomeAssistant, mock_waterfurnace_client: Mock
465+
) -> None:
466+
"""Test import flow with connection error."""
467+
mock_waterfurnace_client.login.side_effect = WFException("Connection failed")
468+
469+
result = cast(
470+
dict[str, Any],
471+
await hass.config_entries.flow.async_init(
472+
DOMAIN,
473+
context={"source": config_entries.SOURCE_IMPORT},
474+
data={
475+
CONF_USERNAME: "test_user",
476+
CONF_PASSWORD: "test_password",
477+
},
478+
),
479+
)
480+
481+
assert result["type"] is FlowResultType.ABORT
482+
assert result["reason"] == "cannot_connect"
483+
484+
485+
async def test_import_flow_invalid_auth(
486+
hass: HomeAssistant, mock_waterfurnace_client: Mock
487+
) -> None:
488+
"""Test import flow with invalid credentials."""
489+
mock_waterfurnace_client.login.side_effect = WFCredentialError(
490+
"Invalid credentials"
491+
)
492+
493+
result = cast(
494+
dict[str, Any],
495+
await hass.config_entries.flow.async_init(
496+
DOMAIN,
497+
context={"source": config_entries.SOURCE_IMPORT},
498+
data={
499+
CONF_USERNAME: "bad_user",
500+
CONF_PASSWORD: "bad_password",
501+
},
502+
),
503+
)
504+
505+
assert result["type"] is FlowResultType.ABORT
506+
assert result["reason"] == "cannot_connect"
507+
508+
509+
async def test_import_flow_unexpected_error(
510+
hass: HomeAssistant, mock_waterfurnace_client: Mock
511+
) -> None:
512+
"""Test import flow with unexpected error."""
513+
mock_waterfurnace_client.login.side_effect = Exception("Unexpected error")
514+
515+
result = cast(
516+
dict[str, Any],
517+
await hass.config_entries.flow.async_init(
518+
DOMAIN,
519+
context={"source": config_entries.SOURCE_IMPORT},
520+
data={
521+
CONF_USERNAME: "test_user",
522+
CONF_PASSWORD: "test_password",
523+
},
524+
),
525+
)
526+
527+
assert result["type"] is FlowResultType.ABORT
528+
assert result["reason"] == "cannot_connect"

0 commit comments

Comments
 (0)