Add config flow to Hydrawise (#95589)

* Add config flow to Hydrawise

* Raise an issue when a YAML config is detected

* Add a test for YAML import

* Add missing __init__.py

* Update CODEOWNERS

* Update requirements_test_all.txt

* Add config flow data to strings.json

* Hande scan_interval not being in YAML on import

* Fix requirements

* Update deprecation dates

* Update requirements_test_all.txt

* Changes from review

* Update homeassistant/components/hydrawise/__init__.py

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* Add already_configured to strings.json

* Add back setup_platform functions

* Apply suggestions from code review

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* Add back setup_platform

* Update requirements_test_all.txt

* Run black on hydrawise/*.py

* Add missing import of HOMEASSISTANT_DOMAIN

* Use more specific errors in config flow

* Add additional tests

* Update config flow to use pydrawise.legacy

* Re-work YAML deprecation issues

* Revert some changes to binary_sensor, as requested in review

* Changes requested during review

* Apply suggestions from code review

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* Remove unused STE_USER_DATA_SCHEMA

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* Update comment in setup_platform

* Re-work the config flow again

* Apply suggestions from code review

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

* Update tests

* Add back the _default_watering_timer attribute

* Bump deprecation dates

* Update requirements_test_all.txt

* Update CODEOWNERS

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
David Knowles 2023-09-23 18:03:07 -04:00 committed by GitHub
parent 28dc17c0b3
commit f8a8fe760d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 478 additions and 57 deletions

View File

@ -537,7 +537,12 @@ omit =
homeassistant/components/hvv_departures/__init__.py
homeassistant/components/hvv_departures/binary_sensor.py
homeassistant/components/hvv_departures/sensor.py
homeassistant/components/hydrawise/*
homeassistant/components/hydrawise/__init__.py
homeassistant/components/hydrawise/binary_sensor.py
homeassistant/components/hydrawise/const.py
homeassistant/components/hydrawise/coordinator.py
homeassistant/components/hydrawise/sensor.py
homeassistant/components/hydrawise/switch.py
homeassistant/components/ialarm/alarm_control_panel.py
homeassistant/components/iammeter/sensor.py
homeassistant/components/iaqualink/binary_sensor.py

View File

@ -562,6 +562,7 @@ build.json @home-assistant/supervisor
/homeassistant/components/hvv_departures/ @vigonotion
/tests/components/hvv_departures/ @vigonotion
/homeassistant/components/hydrawise/ @dknowles2 @ptcryan
/tests/components/hydrawise/ @dknowles2 @ptcryan
/homeassistant/components/hyperion/ @dermotduffy
/tests/components/hyperion/ @dermotduffy
/homeassistant/components/ialarm/ @RyuzakiKK

View File

@ -5,13 +5,19 @@ from pydrawise.legacy import LegacyHydrawise
from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol
from homeassistant.components import persistent_notification
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_SCAN_INTERVAL
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_ACCESS_TOKEN,
CONF_API_KEY,
CONF_SCAN_INTERVAL,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN, LOGGER, NOTIFICATION_ID, NOTIFICATION_TITLE, SCAN_INTERVAL
from .const import DOMAIN, LOGGER, SCAN_INTERVAL
from .coordinator import HydrawiseDataUpdateCoordinator
CONFIG_SCHEMA = vol.Schema(
@ -26,37 +32,49 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Hunter Hydrawise component."""
conf = config[DOMAIN]
access_token = conf[CONF_ACCESS_TOKEN]
scan_interval = conf.get(CONF_SCAN_INTERVAL)
if DOMAIN not in config:
return True
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={CONF_API_KEY: config[DOMAIN][CONF_ACCESS_TOKEN]},
)
)
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up Hydrawise from a config entry."""
access_token = config_entry.data[CONF_API_KEY]
try:
hydrawise = await hass.async_add_executor_job(LegacyHydrawise, access_token)
except (ConnectTimeout, HTTPError) as ex:
LOGGER.error("Unable to connect to Hydrawise cloud service: %s", str(ex))
_show_failure_notification(hass, str(ex))
return False
raise ConfigEntryNotReady(
f"Unable to connect to Hydrawise cloud service: {ex}"
) from ex
if not hydrawise.current_controller:
LOGGER.error("Failed to fetch Hydrawise data")
_show_failure_notification(hass, "Failed to fetch Hydrawise data.")
return False
hass.data[DOMAIN] = HydrawiseDataUpdateCoordinator(hass, hydrawise, scan_interval)
hass.data.setdefault(DOMAIN, {})[
config_entry.entry_id
] = HydrawiseDataUpdateCoordinator(hass, hydrawise, SCAN_INTERVAL)
if not hydrawise.controller_info or not hydrawise.controller_status:
raise ConfigEntryNotReady("Hydrawise data not loaded")
# NOTE: We don't need to call async_config_entry_first_refresh() because
# data is fetched when the Hydrawiser object is instantiated.
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
def _show_failure_notification(hass: HomeAssistant, error: str) -> None:
persistent_notification.create(
hass,
f"Error: {error}<br />You will need to restart hass after fixing.",
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID,
)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -10,6 +10,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
@ -38,6 +39,8 @@ BINARY_SENSOR_KEYS: list[str] = [
desc.key for desc in (BINARY_SENSOR_STATUS, *BINARY_SENSOR_TYPES)
]
# Deprecated since Home Assistant 2023.10.0
# Can be removed completely in 2024.4.0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_MONITORED_CONDITIONS, default=BINARY_SENSOR_KEYS): vol.All(
@ -54,32 +57,39 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up a sensor for a Hydrawise device."""
coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN]
hydrawise: LegacyHydrawise = coordinator.api
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
# We don't need to trigger import flow from here as it's triggered from `__init__.py`
return
entities = []
if BINARY_SENSOR_STATUS.key in monitored_conditions:
entities.append(
HydrawiseBinarySensor(
data=hydrawise.current_controller,
coordinator=coordinator,
description=BINARY_SENSOR_STATUS,
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Hydrawise binary_sensor platform."""
coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
hydrawise: LegacyHydrawise = coordinator.api
entities = [
HydrawiseBinarySensor(
data=hydrawise.current_controller,
coordinator=coordinator,
description=BINARY_SENSOR_STATUS,
)
]
# create a sensor for each zone
for zone in hydrawise.relays:
for description in BINARY_SENSOR_TYPES:
if description.key not in monitored_conditions:
continue
entities.append(
HydrawiseBinarySensor(
data=zone, coordinator=coordinator, description=description
)
)
add_entities(entities, True)
async_add_entities(entities)
class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity):

View File

@ -0,0 +1,111 @@
"""Config flow for the Hydrawise integration."""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
from pydrawise import legacy
from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN
from homeassistant.data_entry_flow import AbortFlow, FlowResult, FlowResultType
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from .const import DOMAIN, LOGGER
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Hydrawise."""
VERSION = 1
async def _create_entry(
self, api_key: str, *, on_failure: Callable[[str], FlowResult]
) -> FlowResult:
"""Create the config entry."""
try:
api = await self.hass.async_add_executor_job(
legacy.LegacyHydrawise, api_key
)
except ConnectTimeout:
return on_failure("timeout_connect")
except HTTPError as ex:
LOGGER.error("Unable to connect to Hydrawise cloud service: %s", ex)
return on_failure("cannot_connect")
if not api.status:
return on_failure("unknown")
await self.async_set_unique_id(f"hydrawise-{api.customer_id}")
self._abort_if_unique_id_configured()
return self.async_create_entry(title="Hydrawise", data={CONF_API_KEY: api_key})
def _import_issue(self, error_type: str) -> FlowResult:
"""Create an issue about a YAML import failure."""
async_create_issue(
self.hass,
DOMAIN,
f"deprecated_yaml_import_issue_{error_type}",
breaks_in_ha_version="2024.4.0",
is_fixable=False,
severity=IssueSeverity.ERROR,
translation_key="deprecated_yaml_import_issue",
translation_placeholders={"error_type": error_type},
)
return self.async_abort(reason=error_type)
def _deprecated_yaml_issue(self) -> None:
"""Create an issue about YAML deprecation."""
async_create_issue(
self.hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2024.4.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Hydrawise",
},
)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial setup."""
if user_input is not None:
api_key = user_input[CONF_API_KEY]
return await self._create_entry(api_key, on_failure=self._show_form)
return self._show_form()
def _show_form(self, error_type: str | None = None) -> FlowResult:
errors = {}
if error_type is not None:
errors["base"] = error_type
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_API_KEY): str}),
errors=errors,
)
async def async_step_import(self, import_data: dict[str, Any]) -> FlowResult:
"""Import data from YAML."""
try:
result = await self._create_entry(
import_data.get(CONF_API_KEY, ""),
on_failure=self._import_issue,
)
except AbortFlow:
self._deprecated_yaml_issue()
raise
if result["type"] == FlowResultType.CREATE_ENTRY:
self._deprecated_yaml_issue()
return result

View File

@ -8,9 +8,6 @@ LOGGER = logging.getLogger(__package__)
ALLOWED_WATERING_TIME = [5, 10, 15, 30, 45, 60]
CONF_WATERING_TIME = "watering_minutes"
NOTIFICATION_ID = "hydrawise_notification"
NOTIFICATION_TITLE = "Hydrawise Setup"
DOMAIN = "hydrawise"
DEFAULT_WATERING_TIME = 15

View File

@ -2,6 +2,7 @@
"domain": "hydrawise",
"name": "Hunter Hydrawise",
"codeowners": ["@dknowles2", "@ptcryan"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/hydrawise",
"iot_class": "cloud_polling",
"loggers": ["pydrawise"],

View File

@ -1,7 +1,6 @@
"""Support for Hydrawise sprinkler sensors."""
from __future__ import annotations
from pydrawise.legacy import LegacyHydrawise
import voluptuous as vol
from homeassistant.components.sensor import (
@ -10,6 +9,7 @@ from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MONITORED_CONDITIONS, UnitOfTime
from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
@ -37,6 +37,8 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
# Deprecated since Home Assistant 2023.10.0
# Can be removed completely in 2024.4.0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_KEYS): vol.All(
@ -56,18 +58,25 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up a sensor for a Hydrawise device."""
coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN]
hydrawise: LegacyHydrawise = coordinator.api
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
# We don't need to trigger import flow from here as it's triggered from `__init__.py`
return
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Hydrawise sensor platform."""
coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
entities = [
HydrawiseSensor(data=zone, coordinator=coordinator, description=description)
for zone in hydrawise.relays
for zone in coordinator.api.relays
for description in SENSOR_TYPES
if description.key in monitored_conditions
]
add_entities(entities, True)
async_add_entities(entities)
class HydrawiseSensor(HydrawiseEntity, SensorEntity):

View File

@ -0,0 +1,25 @@
{
"config": {
"step": {
"user": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
}
},
"issues": {
"deprecated_yaml_import_issue": {
"title": "The Hydrawise YAML configuration import failed",
"description": "Configuring Hydrawise using YAML is being removed but there was an {error_type} error importing your YAML configuration.\n\nEnsure connection to Hydrawise works and restart Home Assistant to try again or remove the Hydrawise YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
}
}
}

View File

@ -3,7 +3,6 @@ from __future__ import annotations
from typing import Any
from pydrawise.legacy import LegacyHydrawise
import voluptuous as vol
from homeassistant.components.switch import (
@ -12,6 +11,7 @@ from homeassistant.components.switch import (
SwitchEntity,
SwitchEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
@ -43,6 +43,8 @@ SWITCH_TYPES: tuple[SwitchEntityDescription, ...] = (
SWITCH_KEYS: list[str] = [desc.key for desc in SWITCH_TYPES]
# Deprecated since Home Assistant 2023.10.0
# Can be removed completely in 2024.4.0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_MONITORED_CONDITIONS, default=SWITCH_KEYS): vol.All(
@ -62,10 +64,20 @@ def setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up a sensor for a Hydrawise device."""
coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN]
hydrawise: LegacyHydrawise = coordinator.api
monitored_conditions: list[str] = config[CONF_MONITORED_CONDITIONS]
default_watering_timer: int = config[CONF_WATERING_TIME]
# We don't need to trigger import flow from here as it's triggered from `__init__.py`
return
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Hydrawise switch platform."""
coordinator: HydrawiseDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
default_watering_timer = DEFAULT_WATERING_TIME
entities = [
HydrawiseSwitch(
@ -74,12 +86,11 @@ def setup_platform(
description=description,
default_watering_timer=default_watering_timer,
)
for zone in hydrawise.relays
for zone in coordinator.api.relays
for description in SWITCH_TYPES
if description.key in monitored_conditions
]
add_entities(entities, True)
async_add_entities(entities)
class HydrawiseSwitch(HydrawiseEntity, SwitchEntity):

View File

@ -206,6 +206,7 @@ FLOWS = {
"huisbaasje",
"hunterdouglas_powerview",
"hvv_departures",
"hydrawise",
"hyperion",
"ialarm",
"iaqualink",

View File

@ -2501,7 +2501,7 @@
"hydrawise": {
"name": "Hunter Hydrawise",
"integration_type": "hub",
"config_flow": false,
"config_flow": true,
"iot_class": "cloud_polling"
},
"hyperion": {

View File

@ -1244,6 +1244,9 @@ pydexcom==0.2.3
# homeassistant.components.discovergy
pydiscovergy==2.0.3
# homeassistant.components.hydrawise
pydrawise==2023.8.0
# homeassistant.components.android_ip_webcam
pydroid-ipcam==2.0.0

View File

@ -0,0 +1 @@
"""Tests for the Hydrawise integration."""

View File

@ -0,0 +1,15 @@
"""Common fixtures for the Hydrawise tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.hydrawise.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry

View File

@ -0,0 +1,213 @@
"""Test the Hydrawise config flow."""
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from requests.exceptions import ConnectTimeout, HTTPError
from homeassistant import config_entries
from homeassistant.components.hydrawise.const import DOMAIN
from homeassistant.const import CONF_API_KEY, CONF_SCAN_INTERVAL
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
import homeassistant.helpers.issue_registry as ir
from tests.common import MockConfigEntry
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
@patch("pydrawise.legacy.LegacyHydrawise")
async def test_form(
mock_api: MagicMock, hass: HomeAssistant, mock_setup_entry: AsyncMock
) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], {"api_key": "abc123"}
)
mock_api.return_value.customer_id = 12345
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Hydrawise"
assert result2["data"] == {"api_key": "abc123"}
assert len(mock_setup_entry.mock_calls) == 1
@patch("pydrawise.legacy.LegacyHydrawise")
async def test_form_api_error(mock_api: MagicMock, hass: HomeAssistant) -> None:
"""Test we handle API errors."""
mock_api.side_effect = HTTPError
init_result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
data = {"api_key": "abc123"}
result = await hass.config_entries.flow.async_configure(
init_result["flow_id"], data
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "cannot_connect"}
mock_api.side_effect = None
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], data)
assert result2["type"] == FlowResultType.CREATE_ENTRY
@patch("pydrawise.legacy.LegacyHydrawise")
async def test_form_connect_timeout(mock_api: MagicMock, hass: HomeAssistant) -> None:
"""Test we handle API errors."""
mock_api.side_effect = ConnectTimeout
init_result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
data = {"api_key": "abc123"}
result = await hass.config_entries.flow.async_configure(
init_result["flow_id"], data
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "timeout_connect"}
mock_api.side_effect = None
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], data)
assert result2["type"] == FlowResultType.CREATE_ENTRY
@patch("pydrawise.legacy.LegacyHydrawise")
async def test_flow_import_success(mock_api: MagicMock, hass: HomeAssistant) -> None:
"""Test that we can import a YAML config."""
mock_api.return_value.status = "All good!"
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_API_KEY: "__api_key__",
CONF_SCAN_INTERVAL: 120,
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "Hydrawise"
assert result["data"] == {
CONF_API_KEY: "__api_key__",
}
issue_registry = ir.async_get(hass)
issue = issue_registry.async_get_issue(
HOMEASSISTANT_DOMAIN, "deprecated_yaml_hydrawise"
)
assert issue.translation_key == "deprecated_yaml"
@patch("pydrawise.legacy.LegacyHydrawise", side_effect=HTTPError)
async def test_flow_import_api_error(mock_api: MagicMock, hass: HomeAssistant) -> None:
"""Test that we handle API errors on YAML import."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_API_KEY: "__api_key__",
CONF_SCAN_INTERVAL: 120,
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "cannot_connect"
issue_registry = ir.async_get(hass)
issue = issue_registry.async_get_issue(
DOMAIN, "deprecated_yaml_import_issue_cannot_connect"
)
assert issue.translation_key == "deprecated_yaml_import_issue"
@patch("pydrawise.legacy.LegacyHydrawise", side_effect=ConnectTimeout)
async def test_flow_import_connect_timeout(
mock_api: MagicMock, hass: HomeAssistant
) -> None:
"""Test that we handle connection timeouts on YAML import."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_API_KEY: "__api_key__",
CONF_SCAN_INTERVAL: 120,
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "timeout_connect"
issue_registry = ir.async_get(hass)
issue = issue_registry.async_get_issue(
DOMAIN, "deprecated_yaml_import_issue_timeout_connect"
)
assert issue.translation_key == "deprecated_yaml_import_issue"
@patch("pydrawise.legacy.LegacyHydrawise")
async def test_flow_import_no_status(mock_api: MagicMock, hass: HomeAssistant) -> None:
"""Test we handle a lack of API status on YAML import."""
mock_api.return_value.status = None
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_API_KEY: "__api_key__",
CONF_SCAN_INTERVAL: 120,
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "unknown"
issue_registry = ir.async_get(hass)
issue = issue_registry.async_get_issue(
DOMAIN, "deprecated_yaml_import_issue_unknown"
)
assert issue.translation_key == "deprecated_yaml_import_issue"
@patch("pydrawise.legacy.LegacyHydrawise")
async def test_flow_import_already_imported(
mock_api: MagicMock, hass: HomeAssistant
) -> None:
"""Test that we can handle a YAML config already imported."""
mock_config_entry = MockConfigEntry(
title="Hydrawise",
domain=DOMAIN,
data={
CONF_API_KEY: "__api_key__",
},
unique_id="hydrawise-CUSTOMER_ID",
)
mock_config_entry.add_to_hass(hass)
mock_api.return_value.customer_id = "CUSTOMER_ID"
mock_api.return_value.status = "All good!"
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={
CONF_API_KEY: "__api_key__",
CONF_SCAN_INTERVAL: 120,
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result.get("reason") == "already_configured"
issue_registry = ir.async_get(hass)
issue = issue_registry.async_get_issue(
HOMEASSISTANT_DOMAIN, "deprecated_yaml_hydrawise"
)
assert issue.translation_key == "deprecated_yaml"