New integration for Comelit SimpleHome (#96552)
* New integration for Comelit SimpleHome * Address first review comments * cleanup * aiocomelit bump and coordinator cleanup * address review comments * Fix some review comments * Use config_entry.unique_id as last resort * review comments * Add config_flow tests * fix pre-commit missing checks * test_conflig_flow coverage to 100% * fix tests * address latest review comments * new ruff rule * address review comments * simplify unique_id
This commit is contained in:
parent
9fdad592c2
commit
ab9d6ce61a
16 changed files with 544 additions and 0 deletions
|
@ -168,6 +168,10 @@ omit =
|
||||||
homeassistant/components/cmus/media_player.py
|
homeassistant/components/cmus/media_player.py
|
||||||
homeassistant/components/coinbase/sensor.py
|
homeassistant/components/coinbase/sensor.py
|
||||||
homeassistant/components/comed_hourly_pricing/sensor.py
|
homeassistant/components/comed_hourly_pricing/sensor.py
|
||||||
|
homeassistant/components/comelit/__init__.py
|
||||||
|
homeassistant/components/comelit/const.py
|
||||||
|
homeassistant/components/comelit/coordinator.py
|
||||||
|
homeassistant/components/comelit/light.py
|
||||||
homeassistant/components/comfoconnect/fan.py
|
homeassistant/components/comfoconnect/fan.py
|
||||||
homeassistant/components/concord232/alarm_control_panel.py
|
homeassistant/components/concord232/alarm_control_panel.py
|
||||||
homeassistant/components/concord232/binary_sensor.py
|
homeassistant/components/concord232/binary_sensor.py
|
||||||
|
|
|
@ -209,6 +209,8 @@ build.json @home-assistant/supervisor
|
||||||
/tests/components/coinbase/ @tombrien
|
/tests/components/coinbase/ @tombrien
|
||||||
/homeassistant/components/color_extractor/ @GenericStudent
|
/homeassistant/components/color_extractor/ @GenericStudent
|
||||||
/tests/components/color_extractor/ @GenericStudent
|
/tests/components/color_extractor/ @GenericStudent
|
||||||
|
/homeassistant/components/comelit/ @chemelli74
|
||||||
|
/tests/components/comelit/ @chemelli74
|
||||||
/homeassistant/components/comfoconnect/ @michaelarnauts
|
/homeassistant/components/comfoconnect/ @michaelarnauts
|
||||||
/tests/components/comfoconnect/ @michaelarnauts
|
/tests/components/comfoconnect/ @michaelarnauts
|
||||||
/homeassistant/components/command_line/ @gjohansson-ST
|
/homeassistant/components/command_line/ @gjohansson-ST
|
||||||
|
|
34
homeassistant/components/comelit/__init__.py
Normal file
34
homeassistant/components/comelit/__init__.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
"""Comelit integration."""
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PIN, Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import ComelitSerialBridge
|
||||||
|
|
||||||
|
PLATFORMS = [Platform.LIGHT]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Comelit platform."""
|
||||||
|
coordinator = ComelitSerialBridge(hass, entry.data[CONF_HOST], entry.data[CONF_PIN])
|
||||||
|
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
coordinator: ComelitSerialBridge = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
await coordinator.api.logout()
|
||||||
|
await coordinator.api.close()
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
145
homeassistant/components/comelit/config_flow.py
Normal file
145
homeassistant/components/comelit/config_flow.py
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
"""Config flow for Comelit integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from aiocomelit import ComeliteSerialBridgeAPi, exceptions as aiocomelit_exceptions
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import core, exceptions
|
||||||
|
from homeassistant.config_entries import ConfigEntry, ConfigFlow
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PIN
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
|
||||||
|
from .const import _LOGGER, DOMAIN
|
||||||
|
|
||||||
|
DEFAULT_HOST = "192.168.1.252"
|
||||||
|
DEFAULT_PIN = "111111"
|
||||||
|
|
||||||
|
|
||||||
|
def user_form_schema(user_input: dict[str, Any] | None) -> vol.Schema:
|
||||||
|
"""Return user form schema."""
|
||||||
|
user_input = user_input or {}
|
||||||
|
return vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_HOST, default=DEFAULT_HOST): str,
|
||||||
|
vol.Optional(CONF_PIN, default=DEFAULT_PIN): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PIN): str})
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_input(
|
||||||
|
hass: core.HomeAssistant, data: dict[str, Any]
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""Validate the user input allows us to connect."""
|
||||||
|
|
||||||
|
api = ComeliteSerialBridgeAPi(data[CONF_HOST], data[CONF_PIN])
|
||||||
|
|
||||||
|
try:
|
||||||
|
await api.login()
|
||||||
|
except aiocomelit_exceptions.CannotConnect as err:
|
||||||
|
raise CannotConnect from err
|
||||||
|
except aiocomelit_exceptions.CannotAuthenticate as err:
|
||||||
|
raise InvalidAuth from err
|
||||||
|
finally:
|
||||||
|
await api.logout()
|
||||||
|
await api.close()
|
||||||
|
|
||||||
|
return {"title": data[CONF_HOST]}
|
||||||
|
|
||||||
|
|
||||||
|
class ComelitConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Comelit."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
_reauth_entry: ConfigEntry | None
|
||||||
|
_reauth_host: str
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle the initial step."""
|
||||||
|
if user_input is None:
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=user_form_schema(user_input)
|
||||||
|
)
|
||||||
|
|
||||||
|
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
|
||||||
|
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
info = await validate_input(self.hass, user_input)
|
||||||
|
except CannotConnect:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except InvalidAuth:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
return self.async_create_entry(title=info["title"], data=user_input)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=user_form_schema(user_input), errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
|
||||||
|
"""Handle reauth flow."""
|
||||||
|
self._reauth_entry = self.hass.config_entries.async_get_entry(
|
||||||
|
self.context["entry_id"]
|
||||||
|
)
|
||||||
|
self._reauth_host = entry_data[CONF_HOST]
|
||||||
|
self.context["title_placeholders"] = {"host": self._reauth_host}
|
||||||
|
return await self.async_step_reauth_confirm()
|
||||||
|
|
||||||
|
async def async_step_reauth_confirm(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle reauth confirm."""
|
||||||
|
assert self._reauth_entry
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
try:
|
||||||
|
await validate_input(
|
||||||
|
self.hass, {CONF_HOST: self._reauth_host} | user_input
|
||||||
|
)
|
||||||
|
except CannotConnect:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except InvalidAuth:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
self.hass.config_entries.async_update_entry(
|
||||||
|
self._reauth_entry,
|
||||||
|
data={
|
||||||
|
CONF_HOST: self._reauth_host,
|
||||||
|
CONF_PIN: user_input[CONF_PIN],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.hass.async_create_task(
|
||||||
|
self.hass.config_entries.async_reload(self._reauth_entry.entry_id)
|
||||||
|
)
|
||||||
|
return self.async_abort(reason="reauth_successful")
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="reauth_confirm",
|
||||||
|
description_placeholders={CONF_HOST: self._reauth_entry.data[CONF_HOST]},
|
||||||
|
data_schema=STEP_REAUTH_DATA_SCHEMA,
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CannotConnect(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate we cannot connect."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAuth(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate there is invalid auth."""
|
6
homeassistant/components/comelit/const.py
Normal file
6
homeassistant/components/comelit/const.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
"""Comelit constants."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
DOMAIN = "comelit"
|
50
homeassistant/components/comelit/coordinator.py
Normal file
50
homeassistant/components/comelit/coordinator.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
"""Support for Comelit."""
|
||||||
|
import asyncio
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from aiocomelit import ComeliteSerialBridgeAPi
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .const import _LOGGER, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
class ComelitSerialBridge(DataUpdateCoordinator):
|
||||||
|
"""Queries Comelit Serial Bridge."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, host: str, pin: int) -> None:
|
||||||
|
"""Initialize the scanner."""
|
||||||
|
|
||||||
|
self._host = host
|
||||||
|
self._pin = pin
|
||||||
|
|
||||||
|
self.api = ComeliteSerialBridgeAPi(host, pin)
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
hass=hass,
|
||||||
|
logger=_LOGGER,
|
||||||
|
name=f"{DOMAIN}-{host}-coordinator",
|
||||||
|
update_interval=timedelta(seconds=5),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
|
"""Update router data."""
|
||||||
|
_LOGGER.debug("Polling Comelit Serial Bridge host: %s", self._host)
|
||||||
|
try:
|
||||||
|
logged = await self.api.login()
|
||||||
|
except (asyncio.exceptions.TimeoutError, aiohttp.ClientConnectorError) as err:
|
||||||
|
_LOGGER.warning("Connection error for %s", self._host)
|
||||||
|
raise UpdateFailed(f"Error fetching data: {repr(err)}") from err
|
||||||
|
|
||||||
|
if not logged:
|
||||||
|
raise ConfigEntryAuthFailed
|
||||||
|
|
||||||
|
devices_data = await self.api.get_all_devices()
|
||||||
|
alarm_data = await self.api.get_alarm_config()
|
||||||
|
await self.api.logout()
|
||||||
|
|
||||||
|
return devices_data | alarm_data
|
78
homeassistant/components/comelit/light.py
Normal file
78
homeassistant/components/comelit/light.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
"""Support for lights."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from aiocomelit import ComelitSerialBridgeObject
|
||||||
|
from aiocomelit.const import LIGHT, LIGHT_OFF, LIGHT_ON
|
||||||
|
|
||||||
|
from homeassistant.components.light import LightEntity
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import ComelitSerialBridge
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up Comelit lights."""
|
||||||
|
|
||||||
|
coordinator: ComelitSerialBridge = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
|
# Use config_entry.entry_id as base for unique_id because no serial number or mac is available
|
||||||
|
async_add_entities(
|
||||||
|
ComelitLightEntity(coordinator, device, config_entry.entry_id)
|
||||||
|
for device in coordinator.data[LIGHT].values()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ComelitLightEntity(CoordinatorEntity[ComelitSerialBridge], LightEntity):
|
||||||
|
"""Light device."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: ComelitSerialBridge,
|
||||||
|
device: ComelitSerialBridgeObject,
|
||||||
|
config_entry_unique_id: str | None,
|
||||||
|
) -> None:
|
||||||
|
"""Init light entity."""
|
||||||
|
self._api = coordinator.api
|
||||||
|
self._device = device
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self._attr_unique_id = f"{config_entry_unique_id}-{device.index}"
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={
|
||||||
|
(DOMAIN, self._attr_unique_id),
|
||||||
|
},
|
||||||
|
manufacturer="Comelit",
|
||||||
|
model="Serial Bridge",
|
||||||
|
name=device.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _light_set_state(self, state: int) -> None:
|
||||||
|
"""Set desired light state."""
|
||||||
|
await self.coordinator.api.light_switch(self._device.index, state)
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the light on."""
|
||||||
|
await self._light_set_state(LIGHT_ON)
|
||||||
|
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity off."""
|
||||||
|
await self._light_set_state(LIGHT_OFF)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return True if entity is on."""
|
||||||
|
return self.coordinator.data[LIGHT][self._device.index].status == LIGHT_ON
|
10
homeassistant/components/comelit/manifest.json
Normal file
10
homeassistant/components/comelit/manifest.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"domain": "comelit",
|
||||||
|
"name": "Comelit SimpleHome",
|
||||||
|
"codeowners": ["@chemelli74"],
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/comelit",
|
||||||
|
"iot_class": "local_polling",
|
||||||
|
"loggers": ["aiocomelit"],
|
||||||
|
"requirements": ["aiocomelit==0.0.5"]
|
||||||
|
}
|
31
homeassistant/components/comelit/strings.json
Normal file
31
homeassistant/components/comelit/strings.json
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"flow_title": "{host}",
|
||||||
|
"step": {
|
||||||
|
"reauth_confirm": {
|
||||||
|
"description": "Please enter the correct PIN for VEDO system: {host}",
|
||||||
|
"data": {
|
||||||
|
"pin": "[%key:common::config_flow::data::pin%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
|
"pin": "[%key:common::config_flow::data::pin%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||||
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,6 +81,7 @@ FLOWS = {
|
||||||
"cloudflare",
|
"cloudflare",
|
||||||
"co2signal",
|
"co2signal",
|
||||||
"coinbase",
|
"coinbase",
|
||||||
|
"comelit",
|
||||||
"control4",
|
"control4",
|
||||||
"coolmaster",
|
"coolmaster",
|
||||||
"cpuspeed",
|
"cpuspeed",
|
||||||
|
|
|
@ -883,6 +883,12 @@
|
||||||
"config_flow": false,
|
"config_flow": false,
|
||||||
"iot_class": "cloud_polling"
|
"iot_class": "cloud_polling"
|
||||||
},
|
},
|
||||||
|
"comelit": {
|
||||||
|
"name": "Comelit SimpleHome",
|
||||||
|
"integration_type": "hub",
|
||||||
|
"config_flow": true,
|
||||||
|
"iot_class": "local_polling"
|
||||||
|
},
|
||||||
"comfoconnect": {
|
"comfoconnect": {
|
||||||
"name": "Zehnder ComfoAir Q",
|
"name": "Zehnder ComfoAir Q",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
|
|
|
@ -208,6 +208,9 @@ aiobafi6==0.8.2
|
||||||
# homeassistant.components.aws
|
# homeassistant.components.aws
|
||||||
aiobotocore==2.1.0
|
aiobotocore==2.1.0
|
||||||
|
|
||||||
|
# homeassistant.components.comelit
|
||||||
|
aiocomelit==0.0.5
|
||||||
|
|
||||||
# homeassistant.components.dhcp
|
# homeassistant.components.dhcp
|
||||||
aiodiscover==1.4.16
|
aiodiscover==1.4.16
|
||||||
|
|
||||||
|
|
|
@ -189,6 +189,9 @@ aiobafi6==0.8.2
|
||||||
# homeassistant.components.aws
|
# homeassistant.components.aws
|
||||||
aiobotocore==2.1.0
|
aiobotocore==2.1.0
|
||||||
|
|
||||||
|
# homeassistant.components.comelit
|
||||||
|
aiocomelit==0.0.5
|
||||||
|
|
||||||
# homeassistant.components.dhcp
|
# homeassistant.components.dhcp
|
||||||
aiodiscover==1.4.16
|
aiodiscover==1.4.16
|
||||||
|
|
||||||
|
|
1
tests/components/comelit/__init__.py
Normal file
1
tests/components/comelit/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for the Comelit SimpleHome integration."""
|
16
tests/components/comelit/const.py
Normal file
16
tests/components/comelit/const.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""Common stuff for Comelit SimpleHome tests."""
|
||||||
|
from homeassistant.components.comelit.const import DOMAIN
|
||||||
|
from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PIN
|
||||||
|
|
||||||
|
MOCK_CONFIG = {
|
||||||
|
DOMAIN: {
|
||||||
|
CONF_DEVICES: [
|
||||||
|
{
|
||||||
|
CONF_HOST: "fake_host",
|
||||||
|
CONF_PIN: "1234",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0]
|
154
tests/components/comelit/test_config_flow.py
Normal file
154
tests/components/comelit/test_config_flow.py
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
"""Tests for Comelit SimpleHome config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from aiocomelit import CannotAuthenticate, CannotConnect
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.comelit.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
|
from .const import MOCK_USER_DATA
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user(hass: HomeAssistant) -> None:
|
||||||
|
"""Test starting a flow by user."""
|
||||||
|
with patch(
|
||||||
|
"aiocomelit.api.ComeliteSerialBridgeAPi.login",
|
||||||
|
), patch(
|
||||||
|
"aiocomelit.api.ComeliteSerialBridgeAPi.logout",
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.comelit.async_setup_entry"
|
||||||
|
) as mock_setup_entry, patch(
|
||||||
|
"requests.get"
|
||||||
|
) as mock_request_get:
|
||||||
|
mock_request_get.return_value.status_code = 200
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], user_input=MOCK_USER_DATA
|
||||||
|
)
|
||||||
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["data"][CONF_HOST] == "fake_host"
|
||||||
|
assert result["data"][CONF_PIN] == "1234"
|
||||||
|
assert not result["result"].unique_id
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_setup_entry.called
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("side_effect", "error"),
|
||||||
|
[
|
||||||
|
(CannotConnect, "cannot_connect"),
|
||||||
|
(CannotAuthenticate, "invalid_auth"),
|
||||||
|
(ConnectionResetError, "unknown"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_exception_connection(hass: HomeAssistant, side_effect, error) -> None:
|
||||||
|
"""Test starting a flow by user with a connection error."""
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"aiocomelit.api.ComeliteSerialBridgeAPi.login",
|
||||||
|
side_effect=side_effect,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], user_input=MOCK_USER_DATA
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"]["base"] == error
|
||||||
|
|
||||||
|
|
||||||
|
async def test_reauth_successful(hass: HomeAssistant) -> None:
|
||||||
|
"""Test starting a reauthentication flow."""
|
||||||
|
|
||||||
|
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||||
|
mock_config.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"aiocomelit.api.ComeliteSerialBridgeAPi.login",
|
||||||
|
), patch(
|
||||||
|
"aiocomelit.api.ComeliteSerialBridgeAPi.logout",
|
||||||
|
), patch("homeassistant.components.comelit.async_setup_entry"), patch(
|
||||||
|
"requests.get"
|
||||||
|
) as mock_request_get:
|
||||||
|
mock_request_get.return_value.status_code = 200
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_REAUTH, "entry_id": mock_config.entry_id},
|
||||||
|
data=mock_config.data,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
CONF_PIN: "other_fake_pin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "reauth_successful"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("side_effect", "error"),
|
||||||
|
[
|
||||||
|
(CannotConnect, "cannot_connect"),
|
||||||
|
(CannotAuthenticate, "invalid_auth"),
|
||||||
|
(ConnectionResetError, "unknown"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_reauth_not_successful(hass: HomeAssistant, side_effect, error) -> None:
|
||||||
|
"""Test starting a reauthentication flow but no connection found."""
|
||||||
|
|
||||||
|
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||||
|
mock_config.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"aiocomelit.api.ComeliteSerialBridgeAPi.login", side_effect=side_effect
|
||||||
|
), patch(
|
||||||
|
"aiocomelit.api.ComeliteSerialBridgeAPi.logout",
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.comelit.async_setup_entry"
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_REAUTH, "entry_id": mock_config.entry_id},
|
||||||
|
data=mock_config.data,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
CONF_PIN: "other_fake_pin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
assert result["errors"]["base"] == error
|
Loading…
Add table
Add a link
Reference in a new issue