Add alarm platform to Comelit (#104718)

* initial work on alarm

* final work on alarm

* coveragerc

* add tests

* add code validation

* remove sensor changes for a dedicated PR

* code optimization and cleanup

* tweaks

* tweak #2

* apply suggestion

* code quality

* code quality #2

* fix cover.py

* api typing

* use base classes where possibile

* apply const as per review comment

* cleanup unload entry

* apply review comments
This commit is contained in:
Simone Chemelli 2023-12-26 18:27:33 +01:00 committed by GitHub
parent c6d1f1ccc8
commit 2cd6c2b6bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 328 additions and 73 deletions

View File

@ -173,6 +173,7 @@ omit =
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/__init__.py
homeassistant/components/comelit/alarm_control_panel.py
homeassistant/components/comelit/const.py homeassistant/components/comelit/const.py
homeassistant/components/comelit/cover.py homeassistant/components/comelit/cover.py
homeassistant/components/comelit/coordinator.py homeassistant/components/comelit/coordinator.py

View File

@ -1,38 +1,66 @@
"""Comelit integration.""" """Comelit integration."""
from aiocomelit.const import BRIDGE
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PIN, CONF_PORT, Platform from homeassistant.const import CONF_HOST, CONF_PIN, CONF_PORT, CONF_TYPE, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DEFAULT_PORT, DOMAIN from .const import DEFAULT_PORT, DOMAIN
from .coordinator import ComelitSerialBridge from .coordinator import ComelitBaseCoordinator, ComelitSerialBridge, ComelitVedoSystem
PLATFORMS = [Platform.COVER, Platform.LIGHT, Platform.SENSOR, Platform.SWITCH] BRIDGE_PLATFORMS = [
Platform.COVER,
Platform.LIGHT,
Platform.SENSOR,
Platform.SWITCH,
]
VEDO_PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Comelit platform.""" """Set up Comelit platform."""
coordinator = ComelitSerialBridge(
hass, coordinator: ComelitBaseCoordinator
entry.data[CONF_HOST], if entry.data.get(CONF_TYPE, BRIDGE) == BRIDGE:
entry.data.get(CONF_PORT, DEFAULT_PORT), coordinator = ComelitSerialBridge(
entry.data[CONF_PIN], hass,
) entry.data[CONF_HOST],
entry.data.get(CONF_PORT, DEFAULT_PORT),
entry.data[CONF_PIN],
)
platforms = BRIDGE_PLATFORMS
else:
coordinator = ComelitVedoSystem(
hass,
entry.data[CONF_HOST],
entry.data.get(CONF_PORT, DEFAULT_PORT),
entry.data[CONF_PIN],
)
platforms = VEDO_PLATFORMS
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, platforms)
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
coordinator: ComelitSerialBridge = hass.data[DOMAIN][entry.entry_id] if entry.data.get(CONF_TYPE, BRIDGE) == BRIDGE:
platforms = BRIDGE_PLATFORMS
else:
platforms = VEDO_PLATFORMS
coordinator: ComelitBaseCoordinator = hass.data[DOMAIN][entry.entry_id]
if unload_ok := await hass.config_entries.async_unload_platforms(entry, platforms):
await coordinator.api.logout() await coordinator.api.logout()
await coordinator.api.close() await coordinator.api.close()
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)

View File

@ -0,0 +1,155 @@
"""Support for Comelit VEDO system."""
from __future__ import annotations
import logging
from aiocomelit.api import ComelitVedoAreaObject
from aiocomelit.const import ALARM_AREAS, AlarmAreaState
from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntity,
AlarmControlPanelEntityFeature,
CodeFormat,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED,
STATE_ALARM_DISARMING,
STATE_ALARM_TRIGGERED,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import ComelitVedoSystem
_LOGGER = logging.getLogger(__name__)
AWAY = "away"
DISABLE = "disable"
HOME = "home"
HOME_P1 = "home_p1"
HOME_P2 = "home_p2"
NIGHT = "night"
ALARM_ACTIONS: dict[str, str] = {
DISABLE: "dis", # Disarm
HOME: "p1", # Arm P1
NIGHT: "p12", # Arm P1+P2
AWAY: "tot", # Arm P1+P2 + IR / volumetric
}
ALARM_AREA_ARMED_STATUS: dict[str, int] = {
HOME_P1: 1,
HOME_P2: 2,
NIGHT: 3,
AWAY: 4,
}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Comelit VEDO system alarm control panel devices."""
coordinator: ComelitVedoSystem = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
ComelitAlarmEntity(coordinator, device, config_entry.entry_id)
for device in coordinator.data[ALARM_AREAS].values()
)
class ComelitAlarmEntity(CoordinatorEntity[ComelitVedoSystem], AlarmControlPanelEntity):
"""Representation of a Ness alarm panel."""
_attr_has_entity_name = True
_attr_name = None
_attr_code_format = CodeFormat.NUMBER
_attr_code_arm_required = False
_attr_supported_features = (
AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_HOME
)
def __init__(
self,
coordinator: ComelitVedoSystem,
area: ComelitVedoAreaObject,
config_entry_entry_id: str,
) -> None:
"""Initialize the alarm panel."""
self._api = coordinator.api
self._area_index = area.index
super().__init__(coordinator)
# Use config_entry.entry_id as base for unique_id
# because no serial number or mac is available
self._attr_unique_id = f"{config_entry_entry_id}-{area.index}"
self._attr_device_info = coordinator.platform_device_info(area, "area")
if area.p2:
self._attr_supported_features |= AlarmControlPanelEntityFeature.ARM_NIGHT
@property
def _area(self) -> ComelitVedoAreaObject:
"""Return area object."""
return self.coordinator.data[ALARM_AREAS][self._area_index]
@property
def available(self) -> bool:
"""Return True if alarm is available."""
if self._area.human_status in [AlarmAreaState.ANOMALY, AlarmAreaState.UNKNOWN]:
return False
return super().available
@property
def state(self) -> StateType:
"""Return the state of the alarm."""
_LOGGER.debug(
"Area %s status is: %s. Armed is %s",
self._area.name,
self._area.human_status,
self._area.armed,
)
if self._area.human_status == AlarmAreaState.ARMED:
if self._area.armed == ALARM_AREA_ARMED_STATUS[AWAY]:
return STATE_ALARM_ARMED_AWAY
if self._area.armed == ALARM_AREA_ARMED_STATUS[NIGHT]:
return STATE_ALARM_ARMED_NIGHT
return STATE_ALARM_ARMED_HOME
{
AlarmAreaState.DISARMED: STATE_ALARM_DISARMED,
AlarmAreaState.ENTRY_DELAY: STATE_ALARM_DISARMING,
AlarmAreaState.EXIT_DELAY: STATE_ALARM_ARMING,
AlarmAreaState.TRIGGERED: STATE_ALARM_TRIGGERED,
}.get(self._area.human_status)
return None
async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
if code != str(self._api.device_pin):
return
await self._api.set_zone_status(self._area.index, ALARM_ACTIONS[DISABLE])
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
await self._api.set_zone_status(self._area.index, ALARM_ACTIONS[AWAY])
async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
await self._api.set_zone_status(self._area.index, ALARM_ACTIONS[HOME])
async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""
await self._api.set_zone_status(self._area.index, ALARM_ACTIONS[NIGHT])

View File

@ -4,16 +4,22 @@ from __future__ import annotations
from collections.abc import Mapping from collections.abc import Mapping
from typing import Any from typing import Any
from aiocomelit import ComeliteSerialBridgeApi, exceptions as aiocomelit_exceptions from aiocomelit import (
ComeliteSerialBridgeApi,
ComelitVedoApi,
exceptions as aiocomelit_exceptions,
)
from aiocomelit.api import ComelitCommonApi
from aiocomelit.const import BRIDGE
import voluptuous as vol import voluptuous as vol
from homeassistant import core, exceptions from homeassistant import core, exceptions
from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.const import CONF_HOST, CONF_PIN, CONF_PORT from homeassistant.const import CONF_HOST, CONF_PIN, CONF_PORT, CONF_TYPE
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from .const import _LOGGER, DEFAULT_PORT, DOMAIN from .const import _LOGGER, DEFAULT_PORT, DEVICE_TYPE_LIST, DOMAIN
DEFAULT_HOST = "192.168.1.252" DEFAULT_HOST = "192.168.1.252"
DEFAULT_PIN = 111111 DEFAULT_PIN = 111111
@ -27,6 +33,7 @@ def user_form_schema(user_input: dict[str, Any] | None) -> vol.Schema:
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int, vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int,
vol.Required(CONF_TYPE, default=BRIDGE): vol.In(DEVICE_TYPE_LIST),
} }
) )
@ -39,7 +46,11 @@ async def validate_input(
) -> dict[str, str]: ) -> dict[str, str]:
"""Validate the user input allows us to connect.""" """Validate the user input allows us to connect."""
api = ComeliteSerialBridgeApi(data[CONF_HOST], data[CONF_PORT], data[CONF_PIN]) api: ComelitCommonApi
if data.get(CONF_TYPE, BRIDGE) == BRIDGE:
api = ComeliteSerialBridgeApi(data[CONF_HOST], data[CONF_PORT], data[CONF_PIN])
else:
api = ComelitVedoApi(data[CONF_HOST], data[CONF_PORT], data[CONF_PIN])
try: try:
await api.login() await api.login()

View File

@ -1,7 +1,10 @@
"""Comelit constants.""" """Comelit constants."""
import logging import logging
from aiocomelit.const import BRIDGE, VEDO
_LOGGER = logging.getLogger(__package__) _LOGGER = logging.getLogger(__package__)
DOMAIN = "comelit" DOMAIN = "comelit"
DEFAULT_PORT = 80 DEFAULT_PORT = 80
DEVICE_TYPE_LIST = [BRIDGE, VEDO]

View File

@ -1,9 +1,17 @@
"""Support for Comelit.""" """Support for Comelit."""
from abc import abstractmethod
from datetime import timedelta from datetime import timedelta
from typing import Any from typing import Any
from aiocomelit import ComeliteSerialBridgeApi, ComelitSerialBridgeObject, exceptions from aiocomelit import (
from aiocomelit.const import BRIDGE ComeliteSerialBridgeApi,
ComelitSerialBridgeObject,
ComelitVedoApi,
ComelitVedoAreaObject,
exceptions,
)
from aiocomelit.api import ComelitCommonApi
from aiocomelit.const import BRIDGE, VEDO
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -14,19 +22,18 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import _LOGGER, DOMAIN from .const import _LOGGER, DOMAIN
class ComelitSerialBridge(DataUpdateCoordinator): class ComelitBaseCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Queries Comelit Serial Bridge.""" """Base coordinator for Comelit Devices."""
_hw_version: str
config_entry: ConfigEntry config_entry: ConfigEntry
api: ComelitCommonApi
def __init__(self, hass: HomeAssistant, host: str, port: int, pin: int) -> None: def __init__(self, hass: HomeAssistant, device: str, host: str) -> None:
"""Initialize the scanner.""" """Initialize the scanner."""
self._device = device
self._host = host self._host = host
self._port = port
self._pin = pin
self.api = ComeliteSerialBridgeApi(host, port, pin)
super().__init__( super().__init__(
hass=hass, hass=hass,
@ -38,43 +45,80 @@ class ComelitSerialBridge(DataUpdateCoordinator):
device_registry.async_get_or_create( device_registry.async_get_or_create(
config_entry_id=self.config_entry.entry_id, config_entry_id=self.config_entry.entry_id,
identifiers={(DOMAIN, self.config_entry.entry_id)}, identifiers={(DOMAIN, self.config_entry.entry_id)},
model=BRIDGE, model=device,
name=f"{BRIDGE} ({self.api.host})", name=f"{device} ({self._host})",
**self.basic_device_info, manufacturer="Comelit",
hw_version=self._hw_version,
) )
@property def platform_device_info(
def basic_device_info(self) -> dict: self,
"""Set basic device info.""" object_class: ComelitVedoAreaObject | ComelitSerialBridgeObject,
object_type: str,
return { ) -> dr.DeviceInfo:
"manufacturer": "Comelit",
"hw_version": "20003101",
}
def platform_device_info(self, device: ComelitSerialBridgeObject) -> dr.DeviceInfo:
"""Set platform device info.""" """Set platform device info."""
return dr.DeviceInfo( return dr.DeviceInfo(
identifiers={ identifiers={
(DOMAIN, f"{self.config_entry.entry_id}-{device.type}-{device.index}") (
DOMAIN,
f"{self.config_entry.entry_id}-{object_type}-{object_class.index}",
)
}, },
via_device=(DOMAIN, self.config_entry.entry_id), via_device=(DOMAIN, self.config_entry.entry_id),
name=device.name, name=object_class.name,
model=f"{BRIDGE} {device.type}", model=f"{self._device} {object_type}",
**self.basic_device_info, manufacturer="Comelit",
hw_version=self._hw_version,
) )
async def _async_update_data(self) -> dict[str, Any]: async def _async_update_data(self) -> dict[str, Any]:
"""Update device data.""" """Update device data."""
_LOGGER.debug("Polling Comelit Serial Bridge host: %s", self._host) _LOGGER.debug("Polling Comelit %s host: %s", self._device, self._host)
try: try:
await self.api.login() await self.api.login()
return await self.api.get_all_devices() return await self._async_update_system_data()
except exceptions.CannotConnect as err: except exceptions.CannotConnect as err:
_LOGGER.warning("Connection error for %s", self._host) _LOGGER.warning("Connection error for %s", self._host)
await self.api.close() await self.api.close()
raise UpdateFailed(f"Error fetching data: {repr(err)}") from err raise UpdateFailed(f"Error fetching data: {repr(err)}") from err
except exceptions.CannotAuthenticate: except exceptions.CannotAuthenticate:
raise ConfigEntryAuthFailed raise ConfigEntryAuthFailed
return {}
@abstractmethod
async def _async_update_system_data(self) -> dict[str, Any]:
"""Class method for updating data."""
class ComelitSerialBridge(ComelitBaseCoordinator):
"""Queries Comelit Serial Bridge."""
_hw_version = "20003101"
api: ComeliteSerialBridgeApi
def __init__(self, hass: HomeAssistant, host: str, port: int, pin: int) -> None:
"""Initialize the scanner."""
self.api = ComeliteSerialBridgeApi(host, port, pin)
super().__init__(hass, BRIDGE, host)
async def _async_update_system_data(self) -> dict[str, Any]:
"""Specific method for updating data."""
return await self.api.get_all_devices()
class ComelitVedoSystem(ComelitBaseCoordinator):
"""Queries Comelit VEDO system."""
_hw_version = "VEDO IP"
api: ComelitVedoApi
def __init__(self, hass: HomeAssistant, host: str, port: int, pin: int) -> None:
"""Initialize the scanner."""
self.api = ComelitVedoApi(host, port, pin)
super().__init__(hass, VEDO, host)
async def _async_update_system_data(self) -> dict[str, Any]:
"""Specific method for updating data."""
return await self.api.get_all_areas_and_zones()

View File

@ -54,7 +54,7 @@ class ComelitCoverEntity(
# Use config_entry.entry_id as base for unique_id # Use config_entry.entry_id as base for unique_id
# because no serial number or mac is available # because no serial number or mac is available
self._attr_unique_id = f"{config_entry_entry_id}-{device.index}" self._attr_unique_id = f"{config_entry_entry_id}-{device.index}"
self._attr_device_info = coordinator.platform_device_info(device) self._attr_device_info = coordinator.platform_device_info(device, device.type)
# Device doesn't provide a status so we assume UNKNOWN at first startup # Device doesn't provide a status so we assume UNKNOWN at first startup
self._last_action: int | None = None self._last_action: int | None = None
self._last_state: str | None = None self._last_state: str | None = None

View File

@ -50,7 +50,7 @@ class ComelitLightEntity(CoordinatorEntity[ComelitSerialBridge], LightEntity):
# Use config_entry.entry_id as base for unique_id # Use config_entry.entry_id as base for unique_id
# because no serial number or mac is available # because no serial number or mac is available
self._attr_unique_id = f"{config_entry_entry_id}-{device.index}" self._attr_unique_id = f"{config_entry_entry_id}-{device.index}"
self._attr_device_info = coordinator.platform_device_info(device) self._attr_device_info = coordinator.platform_device_info(device, device.type)
async def _light_set_state(self, state: int) -> None: async def _light_set_state(self, state: int) -> None:
"""Set desired light state.""" """Set desired light state."""

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/comelit", "documentation": "https://www.home-assistant.io/integrations/comelit",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["aiocomelit"], "loggers": ["aiocomelit"],
"requirements": ["aiocomelit==0.6.2"] "requirements": ["aiocomelit==0.7.0"]
} }

View File

@ -69,7 +69,7 @@ class ComelitSensorEntity(CoordinatorEntity[ComelitSerialBridge], SensorEntity):
# Use config_entry.entry_id as base for unique_id # Use config_entry.entry_id as base for unique_id
# because no serial number or mac is available # because no serial number or mac is available
self._attr_unique_id = f"{config_entry_entry_id}-{device.index}" self._attr_unique_id = f"{config_entry_entry_id}-{device.index}"
self._attr_device_info = coordinator.platform_device_info(device) self._attr_device_info = coordinator.platform_device_info(device, device.type)
self.entity_description = description self.entity_description = description

View File

@ -56,7 +56,7 @@ class ComelitSwitchEntity(CoordinatorEntity[ComelitSerialBridge], SwitchEntity):
# Use config_entry.entry_id as base for unique_id # Use config_entry.entry_id as base for unique_id
# because no serial number or mac is available # because no serial number or mac is available
self._attr_unique_id = f"{config_entry_entry_id}-{device.type}-{device.index}" self._attr_unique_id = f"{config_entry_entry_id}-{device.type}-{device.index}"
self._attr_device_info = coordinator.platform_device_info(device) self._attr_device_info = coordinator.platform_device_info(device, device.type)
if device.type == OTHER: if device.type == OTHER:
self._attr_device_class = SwitchDeviceClass.OUTLET self._attr_device_class = SwitchDeviceClass.OUTLET

View File

@ -215,7 +215,7 @@ aiobafi6==0.9.0
aiobotocore==2.6.0 aiobotocore==2.6.0
# homeassistant.components.comelit # homeassistant.components.comelit
aiocomelit==0.6.2 aiocomelit==0.7.0
# homeassistant.components.dhcp # homeassistant.components.dhcp
aiodiscover==1.6.0 aiodiscover==1.6.0

View File

@ -194,7 +194,7 @@ aiobafi6==0.9.0
aiobotocore==2.6.0 aiobotocore==2.6.0
# homeassistant.components.comelit # homeassistant.components.comelit
aiocomelit==0.6.2 aiocomelit==0.7.0
# homeassistant.components.dhcp # homeassistant.components.dhcp
aiodiscover==1.6.0 aiodiscover==1.6.0

View File

@ -1,7 +1,9 @@
"""Common stuff for Comelit SimpleHome tests.""" """Common stuff for Comelit SimpleHome tests."""
from aiocomelit.const import VEDO
from homeassistant.components.comelit.const import DOMAIN from homeassistant.components.comelit.const import DOMAIN
from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PIN, CONF_PORT from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PIN, CONF_PORT, CONF_TYPE
MOCK_CONFIG = { MOCK_CONFIG = {
DOMAIN: { DOMAIN: {
@ -10,11 +12,18 @@ MOCK_CONFIG = {
CONF_HOST: "fake_host", CONF_HOST: "fake_host",
CONF_PORT: 80, CONF_PORT: 80,
CONF_PIN: 1234, CONF_PIN: 1234,
} },
{
CONF_HOST: "fake_vedo_host",
CONF_PORT: 8080,
CONF_PIN: 1234,
CONF_TYPE: VEDO,
},
] ]
} }
} }
MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0] MOCK_USER_BRIDGE_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0]
MOCK_USER_VEDO_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][1]
FAKE_PIN = 5678 FAKE_PIN = 5678

View File

@ -1,4 +1,5 @@
"""Tests for Comelit SimpleHome config flow.""" """Tests for Comelit SimpleHome config flow."""
from typing import Any
from unittest.mock import patch from unittest.mock import patch
from aiocomelit import CannotAuthenticate, CannotConnect from aiocomelit import CannotAuthenticate, CannotConnect
@ -10,24 +11,27 @@ from homeassistant.const import CONF_HOST, CONF_PIN, CONF_PORT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from .const import FAKE_PIN, MOCK_USER_DATA from .const import FAKE_PIN, MOCK_USER_BRIDGE_DATA, MOCK_USER_VEDO_DATA
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
async def test_user(hass: HomeAssistant) -> None: @pytest.mark.parametrize(
("class_api", "user_input"),
[
("ComeliteSerialBridgeApi", MOCK_USER_BRIDGE_DATA),
("ComelitVedoApi", MOCK_USER_VEDO_DATA),
],
)
async def test_full_flow(
hass: HomeAssistant, class_api: str, user_input: dict[str, Any]
) -> None:
"""Test starting a flow by user.""" """Test starting a flow by user."""
with patch( with patch(
"aiocomelit.api.ComeliteSerialBridgeApi.login", f"aiocomelit.api.{class_api}.login",
), patch( ), patch(
"aiocomelit.api.ComeliteSerialBridgeApi.logout", f"aiocomelit.api.{class_api}.logout",
), patch( ), patch("homeassistant.components.comelit.async_setup_entry") as mock_setup_entry:
"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( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
@ -35,12 +39,12 @@ async def test_user(hass: HomeAssistant) -> None:
assert result["step_id"] == "user" assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_USER_DATA result["flow_id"], user_input=user_input
) )
assert result["type"] == FlowResultType.CREATE_ENTRY assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"][CONF_HOST] == "fake_host" assert result["data"][CONF_HOST] == user_input[CONF_HOST]
assert result["data"][CONF_PORT] == 80 assert result["data"][CONF_PORT] == user_input[CONF_PORT]
assert result["data"][CONF_PIN] == 1234 assert result["data"][CONF_PIN] == user_input[CONF_PIN]
assert not result["result"].unique_id assert not result["result"].unique_id
await hass.async_block_till_done() await hass.async_block_till_done()
@ -73,7 +77,7 @@ async def test_exception_connection(hass: HomeAssistant, side_effect, error) ->
"homeassistant.components.comelit.async_setup_entry", "homeassistant.components.comelit.async_setup_entry",
): ):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_USER_DATA result["flow_id"], user_input=MOCK_USER_BRIDGE_DATA
) )
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
@ -84,7 +88,7 @@ async def test_exception_connection(hass: HomeAssistant, side_effect, error) ->
async def test_reauth_successful(hass: HomeAssistant) -> None: async def test_reauth_successful(hass: HomeAssistant) -> None:
"""Test starting a reauthentication flow.""" """Test starting a reauthentication flow."""
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_BRIDGE_DATA)
mock_config.add_to_hass(hass) mock_config.add_to_hass(hass)
with patch( with patch(
@ -128,7 +132,7 @@ async def test_reauth_successful(hass: HomeAssistant) -> None:
async def test_reauth_not_successful(hass: HomeAssistant, side_effect, error) -> None: async def test_reauth_not_successful(hass: HomeAssistant, side_effect, error) -> None:
"""Test starting a reauthentication flow but no connection found.""" """Test starting a reauthentication flow but no connection found."""
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA) mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_BRIDGE_DATA)
mock_config.add_to_hass(hass) mock_config.add_to_hass(hass)
with patch( with patch(