Add new integration for Dio Chacon cover devices (#116267)

* Dio Chacon integration addition with config flow and cover entity

* Addition of model information for device

* Addition of light and service to force reloading states

* Logger improvements

* Convert light to switch and usage of v1.0.0 of the api

* 100% for tests coverage

* Invalid credential implementation and rebase on latest ha dev code

* Simplify PR with only one platform

* Ruff correction

* restore original .gitignore content

* Correction of cover state bug when using cover when using actions on cover group.

* Begin of corrections following review.

* unit tests correction

* Refactor with a coordinator as asked by review

* Implemented a post constructor callback init method via dio-chacon-api-1.0.2. Improved typing.

* Corrections for 2nd review

* Reimplemented without coordinator as reviewed with Joostlek

* Review improvement

* generalize callback in entity

* Other review improvements

* Refactored tests for readability

* Test 100% operationals

* Tests review corrections

* Tests review corrections

* Review tests improvements

* simplified tests with snapshots and callback method

* Final fixes

* Final fixes

* Final fixes

* Rename to chacon_dio

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
cnico 2024-07-04 16:45:20 +02:00 committed by GitHub
parent 28f06cb5a0
commit 092e362f01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 830 additions and 0 deletions

View File

@ -237,6 +237,8 @@ build.json @home-assistant/supervisor
/tests/components/ccm15/ @ocalvo
/homeassistant/components/cert_expiry/ @jjlawren
/tests/components/cert_expiry/ @jjlawren
/homeassistant/components/chacon_dio/ @cnico
/tests/components/chacon_dio/ @cnico
/homeassistant/components/cisco_ios/ @fbradyirl
/homeassistant/components/cisco_mobility_express/ @fbradyirl
/homeassistant/components/cisco_webex_teams/ @fbradyirl

View File

@ -0,0 +1,80 @@
"""The chacon_dio integration."""
from dataclasses import dataclass
import logging
from typing import Any
from dio_chacon_wifi_api import DIOChaconAPIClient
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_PASSWORD,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.core import Event, HomeAssistant
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.COVER]
@dataclass
class ChaconDioData:
"""Chacon Dio data class."""
client: DIOChaconAPIClient
list_devices: list[dict[str, Any]]
type ChaconDioConfigEntry = ConfigEntry[ChaconDioData]
async def async_setup_entry(hass: HomeAssistant, entry: ChaconDioConfigEntry) -> bool:
"""Set up chacon_dio from a config entry."""
config = entry.data
username = config[CONF_USERNAME]
password = config[CONF_PASSWORD]
dio_chacon_id = entry.unique_id
_LOGGER.debug("Initializing Chacon Dio client %s, %s", username, dio_chacon_id)
client = DIOChaconAPIClient(
username,
password,
dio_chacon_id,
)
found_devices = await client.search_all_devices(with_state=True)
list_devices = list(found_devices.values())
_LOGGER.debug("List of devices %s", list_devices)
entry.runtime_data = ChaconDioData(
client=client,
list_devices=list_devices,
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
# Disconnect the permanent websocket connection of home assistant on shutdown
async def _async_disconnect_websocket(_: Event) -> None:
await client.disconnect()
entry.async_on_unload(
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _async_disconnect_websocket
)
)
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):
await entry.runtime_data.client.disconnect()
return unload_ok

View File

@ -0,0 +1,67 @@
"""Config flow for chacon_dio integration."""
from __future__ import annotations
import logging
from typing import Any
from dio_chacon_wifi_api import DIOChaconAPIClient
from dio_chacon_wifi_api.exceptions import DIOChaconAPIError, DIOChaconInvalidAuthError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)
class ChaconDioConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for chacon_dio."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
client = DIOChaconAPIClient(
user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
)
try:
_user_id: str = await client.get_user_id()
except DIOChaconAPIError:
errors["base"] = "cannot_connect"
except DIOChaconInvalidAuthError:
errors["base"] = "invalid_auth"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(_user_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=f"Chacon DiO {user_input[CONF_USERNAME]}",
data=user_input,
)
finally:
await client.disconnect()
return self.async_show_form(
step_id="user",
data_schema=DATA_SCHEMA,
errors=errors,
)

View File

@ -0,0 +1,5 @@
"""Constants for the chacon_dio integration."""
DOMAIN = "chacon_dio"
MANUFACTURER = "Chacon"

View File

@ -0,0 +1,124 @@
"""Cover Platform for Chacon Dio REV-SHUTTER devices."""
import logging
from typing import Any
from dio_chacon_wifi_api.const import DeviceTypeEnum, ShutterMoveEnum
from homeassistant.components.cover import (
ATTR_POSITION,
CoverDeviceClass,
CoverEntity,
CoverEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ChaconDioConfigEntry
from .entity import ChaconDioEntity
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ChaconDioConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Chacon Dio cover devices."""
data = config_entry.runtime_data
client = data.client
async_add_entities(
ChaconDioCover(client, device)
for device in data.list_devices
if device["type"] == DeviceTypeEnum.SHUTTER.value
)
class ChaconDioCover(ChaconDioEntity, CoverEntity):
"""Object for controlling a Chacon Dio cover."""
_attr_device_class = CoverDeviceClass.SHUTTER
_attr_name = None
_attr_supported_features = (
CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
| CoverEntityFeature.STOP
| CoverEntityFeature.SET_POSITION
)
def _update_attr(self, data: dict[str, Any]) -> None:
"""Recomputes the attributes values either at init or when the device state changes."""
self._attr_available = data["connected"]
self._attr_current_cover_position = data["openlevel"]
self._attr_is_closing = data["movement"] == ShutterMoveEnum.DOWN.value
self._attr_is_opening = data["movement"] == ShutterMoveEnum.UP.value
self._attr_is_closed = self._attr_current_cover_position == 0
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close the cover.
Closed status is effective after the server callback that triggers callback_device_state.
"""
_LOGGER.debug(
"Close cover %s , %s, %s",
self.target_id,
self._attr_name,
self.is_closed,
)
# closes effectively only if cover is not already closing and not fully closed
if not self._attr_is_closing and not self.is_closed:
self._attr_is_closing = True
self.async_write_ha_state()
await self.client.move_shutter_direction(
self.target_id, ShutterMoveEnum.DOWN
)
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover.
Opened status is effective after the server callback that triggers callback_device_state.
"""
_LOGGER.debug(
"Open cover %s , %s, %s",
self.target_id,
self._attr_name,
self.current_cover_position,
)
# opens effectively only if cover is not already opening and not fully opened
if not self._attr_is_opening and self.current_cover_position != 100:
self._attr_is_opening = True
self.async_write_ha_state()
await self.client.move_shutter_direction(self.target_id, ShutterMoveEnum.UP)
async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop the cover."""
_LOGGER.debug("Stop cover %s , %s", self.target_id, self._attr_name)
self._attr_is_opening = False
self._attr_is_closing = False
self.async_write_ha_state()
await self.client.move_shutter_direction(self.target_id, ShutterMoveEnum.STOP)
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Set the cover open position in percentage.
Closing or opening status is effective after the server callback that triggers callback_device_state.
"""
position: int = kwargs[ATTR_POSITION]
_LOGGER.debug(
"Set cover position %i, %s , %s", position, self.target_id, self._attr_name
)
await self.client.move_shutter_percentage(self.target_id, position)

View File

@ -0,0 +1,53 @@
"""Base entity for the Chacon Dio entity."""
import logging
from typing import Any
from dio_chacon_wifi_api import DIOChaconAPIClient
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity
from .const import DOMAIN, MANUFACTURER
_LOGGER = logging.getLogger(__name__)
class ChaconDioEntity(Entity):
"""Implements a common class elements representing the Chacon Dio entity."""
_attr_should_poll = False
_attr_has_entity_name = True
def __init__(self, client: DIOChaconAPIClient, device: dict[str, Any]) -> None:
"""Initialize Chacon Dio entity."""
self.client = client
self.target_id: str = device["id"]
self._attr_unique_id = self.target_id
self._attr_device_info: DeviceInfo | None = DeviceInfo(
identifiers={(DOMAIN, self.target_id)},
manufacturer=MANUFACTURER,
name=device["name"],
model=device["model"],
)
self._update_attr(device)
def _update_attr(self, data: dict[str, Any]) -> None:
"""Recomputes the attributes values."""
async def async_added_to_hass(self) -> None:
"""Register the callback for server side events."""
await super().async_added_to_hass()
self.client.set_callback_device_state_by_device(
self.target_id, self.callback_device_state
)
def callback_device_state(self, data: dict[str, Any]) -> None:
"""Receive callback for device state notification pushed from the server."""
_LOGGER.debug("Data received from server %s", data)
self._update_attr(data)
self.async_write_ha_state()

View File

@ -0,0 +1,10 @@
{
"domain": "chacon_dio",
"name": "Chacon DiO",
"codeowners": ["@cnico"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/chacon_dio",
"iot_class": "cloud_push",
"loggers": ["dio_chacon_api"],
"requirements": ["dio-chacon-wifi-api==1.1.0"]
}

View File

@ -0,0 +1,20 @@
{
"config": {
"step": {
"user": {
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"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%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}

View File

@ -98,6 +98,7 @@ FLOWS = {
"cast",
"ccm15",
"cert_expiry",
"chacon_dio",
"cloudflare",
"co2signal",
"coinbase",

View File

@ -878,6 +878,12 @@
"config_flow": true,
"iot_class": "cloud_polling"
},
"chacon_dio": {
"name": "Chacon DiO",
"integration_type": "hub",
"config_flow": true,
"iot_class": "cloud_push"
},
"channels": {
"name": "Channels",
"integration_type": "hub",

View File

@ -734,6 +734,9 @@ devolo-home-control-api==0.18.3
# homeassistant.components.devolo_home_network
devolo-plc-api==1.4.1
# homeassistant.components.chacon_dio
dio-chacon-wifi-api==1.1.0
# homeassistant.components.directv
directv==0.4.0

View File

@ -615,6 +615,9 @@ devolo-home-control-api==0.18.3
# homeassistant.components.devolo_home_network
devolo-plc-api==1.4.1
# homeassistant.components.chacon_dio
dio-chacon-wifi-api==1.1.0
# homeassistant.components.directv
directv==0.4.0

View File

@ -0,0 +1,13 @@
"""Tests for the Chacon Dio integration."""
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Fixture for setting up the component."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

View File

@ -0,0 +1,71 @@
"""Common fixtures for the chacon_dio tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant.components.chacon_dio.const import DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from tests.common import MockConfigEntry
MOCK_COVER_DEVICE = {
"L4HActuator_idmock1": {
"id": "L4HActuator_idmock1",
"name": "Shutter mock 1",
"type": "SHUTTER",
"model": "CERSwd-3B_1.0.6",
"connected": True,
"openlevel": 75,
"movement": "stop",
}
}
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.chacon_dio.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Mock the config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id="test_entry_unique_id",
data={
CONF_USERNAME: "dummylogin",
CONF_PASSWORD: "dummypass",
},
)
@pytest.fixture
def mock_dio_chacon_client() -> Generator[AsyncMock]:
"""Mock a Dio Chacon client."""
with (
patch(
"homeassistant.components.chacon_dio.DIOChaconAPIClient",
autospec=True,
) as mock_client,
patch(
"homeassistant.components.chacon_dio.config_flow.DIOChaconAPIClient",
new=mock_client,
),
):
client = mock_client.return_value
# Default values for the tests using this mock :
client.get_user_id.return_value = "dummy-user-id"
client.search_all_devices.return_value = MOCK_COVER_DEVICE
client.move_shutter_direction.return_value = {}
client.disconnect.return_value = {}
yield client

View File

@ -0,0 +1,50 @@
# serializer version: 1
# name: test_entities[cover.shutter_mock_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'cover',
'entity_category': None,
'entity_id': 'cover.shutter_mock_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <CoverDeviceClass.SHUTTER: 'shutter'>,
'original_icon': None,
'original_name': None,
'platform': 'chacon_dio',
'previous_unique_id': None,
'supported_features': <CoverEntityFeature: 15>,
'translation_key': None,
'unique_id': 'L4HActuator_idmock1',
'unit_of_measurement': None,
})
# ---
# name: test_entities[cover.shutter_mock_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_position': 75,
'device_class': 'shutter',
'friendly_name': 'Shutter mock 1',
'supported_features': <CoverEntityFeature: 15>,
}),
'context': <ANY>,
'entity_id': 'cover.shutter_mock_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'open',
})
# ---

View File

@ -0,0 +1,122 @@
"""Test the chacon_dio config flow."""
from unittest.mock import AsyncMock
from dio_chacon_wifi_api.exceptions import DIOChaconAPIError, DIOChaconInvalidAuthError
import pytest
from homeassistant.components.chacon_dio.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
async def test_full_flow(
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_dio_chacon_client: AsyncMock
) -> None:
"""Test the full flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert not result["errors"]
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={
CONF_USERNAME: "dummylogin",
CONF_PASSWORD: "dummypass",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "Chacon DiO dummylogin"
assert result["result"].unique_id == "dummy-user-id"
assert result["data"] == {
CONF_USERNAME: "dummylogin",
CONF_PASSWORD: "dummypass",
}
@pytest.mark.parametrize(
("exception", "expected"),
[
(Exception("Bad request Boy :) --"), {"base": "unknown"}),
(DIOChaconInvalidAuthError, {"base": "invalid_auth"}),
(DIOChaconAPIError, {"base": "cannot_connect"}),
],
)
async def test_errors(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_dio_chacon_client: AsyncMock,
exception: Exception,
expected: dict[str, str],
) -> None:
"""Test we handle any error."""
mock_dio_chacon_client.get_user_id.side_effect = exception
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={
CONF_USERNAME: "nada",
CONF_PASSWORD: "nadap",
},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == expected
# Test of recover in normal state after correction of the 1st error
mock_dio_chacon_client.get_user_id.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "dummylogin",
CONF_PASSWORD: "dummypass",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "Chacon DiO dummylogin"
assert result["result"].unique_id == "dummy-user-id"
assert result["data"] == {
CONF_USERNAME: "dummylogin",
CONF_PASSWORD: "dummypass",
}
async def test_duplicate_entry(
hass: HomeAssistant,
mock_dio_chacon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test abort when setting up duplicate entry."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert not result["errors"]
mock_dio_chacon_client.get_user_id.return_value = "test_entry_unique_id"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "dummylogin",
CONF_PASSWORD: "dummypass",
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"

View File

@ -0,0 +1,157 @@
"""Test the Chacon Dio cover."""
from collections.abc import Callable
from unittest.mock import AsyncMock
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_POSITION,
DOMAIN as COVER_DOMAIN,
SERVICE_CLOSE_COVER,
SERVICE_OPEN_COVER,
SERVICE_SET_COVER_POSITION,
SERVICE_STOP_COVER,
STATE_CLOSING,
STATE_OPEN,
STATE_OPENING,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
COVER_ENTITY_ID = "cover.shutter_mock_1"
async def test_entities(
hass: HomeAssistant,
mock_dio_chacon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the creation and values of the Chacon Dio covers."""
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_cover_actions(
hass: HomeAssistant,
mock_dio_chacon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test the creation and values of the Chacon Dio covers."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: COVER_ENTITY_ID},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get(COVER_ENTITY_ID)
assert state.state == STATE_CLOSING
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_STOP_COVER,
{ATTR_ENTITY_ID: COVER_ENTITY_ID},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get(COVER_ENTITY_ID)
assert state.state == STATE_OPEN
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: COVER_ENTITY_ID},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get(COVER_ENTITY_ID)
assert state.state == STATE_OPENING
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_SET_COVER_POSITION,
{ATTR_POSITION: 25, ATTR_ENTITY_ID: COVER_ENTITY_ID},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get(COVER_ENTITY_ID)
assert state.state == STATE_OPENING
async def test_cover_callbacks(
hass: HomeAssistant,
mock_dio_chacon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test the creation and values of the Chacon Dio covers."""
await setup_integration(hass, mock_config_entry)
# Server side callback tests
# We find the callback method on the mock client
callback_device_state_function: Callable = (
mock_dio_chacon_client.set_callback_device_state_by_device.call_args[0][1]
)
# Define a method to simply call it
async def _callback_device_state_function(open_level: int, movement: str) -> None:
callback_device_state_function(
{
"id": "L4HActuator_idmock1",
"connected": True,
"openlevel": open_level,
"movement": movement,
}
)
await hass.async_block_till_done()
# And call it to effectively launch the callback as the server would do
await _callback_device_state_function(79, "stop")
state = hass.states.get(COVER_ENTITY_ID)
assert state
assert state.attributes.get(ATTR_CURRENT_POSITION) == 79
assert state.state == STATE_OPEN
await _callback_device_state_function(90, "up")
state = hass.states.get(COVER_ENTITY_ID)
assert state
assert state.attributes.get(ATTR_CURRENT_POSITION) == 90
assert state.state == STATE_OPENING
await _callback_device_state_function(60, "down")
state = hass.states.get(COVER_ENTITY_ID)
assert state
assert state.attributes.get(ATTR_CURRENT_POSITION) == 60
assert state.state == STATE_CLOSING
async def test_no_cover_found(
hass: HomeAssistant,
mock_dio_chacon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test the cover absence."""
mock_dio_chacon_client.search_all_devices.return_value = None
await setup_integration(hass, mock_config_entry)
assert not hass.states.get(COVER_ENTITY_ID)

View File

@ -0,0 +1,43 @@
"""Test the Dio Chacon Cover init."""
from unittest.mock import AsyncMock
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
async def test_cover_unload_entry(
hass: HomeAssistant,
mock_dio_chacon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the creation and values of the Dio Chacon covers."""
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.LOADED
await hass.config_entries.async_unload(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
mock_dio_chacon_client.disconnect.assert_called()
async def test_cover_shutdown_event(
hass: HomeAssistant,
mock_dio_chacon_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the creation and values of the Dio Chacon covers."""
await setup_integration(hass, mock_config_entry)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
mock_dio_chacon_client.disconnect.assert_called()