Add basic test support to Homematic IP Cloud (#27228)
* Add basic test support to Homematic IP Cloud * move test data address comments
This commit is contained in:
parent
2c6a869bc6
commit
476f24e451
9 changed files with 5672 additions and 10 deletions
|
@ -115,6 +115,7 @@ class HomematicipAccesspointStatus(HomematicipGenericDevice):
|
||||||
|
|
||||||
def __init__(self, home: AsyncHome) -> None:
|
def __init__(self, home: AsyncHome) -> None:
|
||||||
"""Initialize access point device."""
|
"""Initialize access point device."""
|
||||||
|
home.modelType = "HmIP-HAP"
|
||||||
super().__init__(home, home)
|
super().__init__(home, home)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
75
tests/components/homematicip_cloud/conftest.py
Normal file
75
tests/components/homematicip_cloud/conftest.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
"""Initializer helpers for HomematicIP fake server."""
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from homematicip.aio.connection import AsyncConnection
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.homematicip_cloud import (
|
||||||
|
DOMAIN as HMIPC_DOMAIN,
|
||||||
|
const as hmipc,
|
||||||
|
hap as hmip_hap,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .helper import AUTH_TOKEN, HAPID, HomeTemplate
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, mock_coro
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="mock_connection")
|
||||||
|
def mock_connection_fixture():
|
||||||
|
"""Return a mockked connection."""
|
||||||
|
connection = MagicMock(spec=AsyncConnection)
|
||||||
|
|
||||||
|
def _rest_call_side_effect(path, body=None):
|
||||||
|
return path, body
|
||||||
|
|
||||||
|
connection._restCall.side_effect = _rest_call_side_effect # pylint: disable=W0212
|
||||||
|
connection.api_call.return_value = mock_coro(True)
|
||||||
|
|
||||||
|
return connection
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="default_mock_home")
|
||||||
|
def default_mock_home_fixture(mock_connection):
|
||||||
|
"""Create a fake homematic async home."""
|
||||||
|
return HomeTemplate(connection=mock_connection).init_home().get_async_home_mock()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="hmip_config_entry")
|
||||||
|
def hmip_config_entry_fixture():
|
||||||
|
"""Create a fake config entriy for homematic ip cloud."""
|
||||||
|
entry_data = {
|
||||||
|
hmipc.HMIPC_HAPID: HAPID,
|
||||||
|
hmipc.HMIPC_AUTHTOKEN: AUTH_TOKEN,
|
||||||
|
hmipc.HMIPC_NAME: "",
|
||||||
|
}
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
version=1,
|
||||||
|
domain=HMIPC_DOMAIN,
|
||||||
|
title=HAPID,
|
||||||
|
data=entry_data,
|
||||||
|
source="import",
|
||||||
|
connection_class=config_entries.CONN_CLASS_CLOUD_PUSH,
|
||||||
|
system_options={"disable_new_entities": False},
|
||||||
|
)
|
||||||
|
|
||||||
|
return config_entry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="default_mock_hap")
|
||||||
|
async def default_mock_hap_fixture(
|
||||||
|
hass: HomeAssistant, default_mock_home, hmip_config_entry
|
||||||
|
):
|
||||||
|
"""Create a fake homematic access point."""
|
||||||
|
hass.config.components.add(HMIPC_DOMAIN)
|
||||||
|
hap = hmip_hap.HomematicipHAP(hass, hmip_config_entry)
|
||||||
|
with patch.object(hap, "get_hap", return_value=mock_coro(default_mock_home)):
|
||||||
|
assert await hap.async_setup() is True
|
||||||
|
|
||||||
|
hass.data[HMIPC_DOMAIN] = {HAPID: hap}
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
return hap
|
128
tests/components/homematicip_cloud/helper.py
Normal file
128
tests/components/homematicip_cloud/helper.py
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
"""Helper for HomematicIP Cloud Tests."""
|
||||||
|
import json
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from homematicip.aio.class_maps import (
|
||||||
|
TYPE_CLASS_MAP,
|
||||||
|
TYPE_GROUP_MAP,
|
||||||
|
TYPE_SECURITY_EVENT_MAP,
|
||||||
|
)
|
||||||
|
from homematicip.aio.home import AsyncHome
|
||||||
|
from homematicip.home import Home
|
||||||
|
|
||||||
|
from tests.common import load_fixture
|
||||||
|
|
||||||
|
HAPID = "Mock_HAP"
|
||||||
|
AUTH_TOKEN = "1234"
|
||||||
|
HOME_JSON = "homematicip_cloud.json"
|
||||||
|
|
||||||
|
|
||||||
|
def get_and_check_entity_basics(
|
||||||
|
hass, default_mock_hap, entity_id, entity_name, device_model
|
||||||
|
):
|
||||||
|
"""Get and test basic device."""
|
||||||
|
ha_entity = hass.states.get(entity_id)
|
||||||
|
assert ha_entity is not None
|
||||||
|
assert ha_entity.attributes["model_type"] == device_model
|
||||||
|
assert ha_entity.name == entity_name
|
||||||
|
|
||||||
|
hmip_device = default_mock_hap.home.template.search_mock_device_by_id(
|
||||||
|
ha_entity.attributes["id"]
|
||||||
|
)
|
||||||
|
assert hmip_device is not None
|
||||||
|
return ha_entity, hmip_device
|
||||||
|
|
||||||
|
|
||||||
|
async def async_manipulate_test_data(
|
||||||
|
hass, hmip_device, attribute, new_value, channel=1
|
||||||
|
):
|
||||||
|
"""Set new value on hmip device."""
|
||||||
|
if channel == 1:
|
||||||
|
setattr(hmip_device, attribute, new_value)
|
||||||
|
functional_channel = hmip_device.functionalChannels[channel]
|
||||||
|
setattr(functional_channel, attribute, new_value)
|
||||||
|
|
||||||
|
hmip_device.fire_update_event()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
class HomeTemplate(Home):
|
||||||
|
"""
|
||||||
|
Home template as builder for home mock.
|
||||||
|
|
||||||
|
It is based on the upstream libs home class to generate hmip devices
|
||||||
|
and groups based on the given homematicip_cloud.json.
|
||||||
|
|
||||||
|
All further testing activities should be done by using the AsyncHome mock,
|
||||||
|
that is generated by get_async_home_mock(self).
|
||||||
|
|
||||||
|
The class also generated mocks of devices and groups for further testing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_typeClassMap = TYPE_CLASS_MAP
|
||||||
|
_typeGroupMap = TYPE_GROUP_MAP
|
||||||
|
_typeSecurityEventMap = TYPE_SECURITY_EVENT_MAP
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
"""Init template with connection."""
|
||||||
|
super().__init__(connection=connection)
|
||||||
|
self.mock_devices = []
|
||||||
|
self.mock_groups = []
|
||||||
|
|
||||||
|
def init_home(self, json_path=HOME_JSON):
|
||||||
|
"""Init template with json."""
|
||||||
|
json_state = json.loads(load_fixture(HOME_JSON), encoding="UTF-8")
|
||||||
|
self.update_home(json_state=json_state, clearConfig=True)
|
||||||
|
self._generate_mocks()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _generate_mocks(self):
|
||||||
|
"""Generate mocks for groups and devices."""
|
||||||
|
for device in self.devices:
|
||||||
|
self.mock_devices.append(_get_mock(device))
|
||||||
|
for group in self.groups:
|
||||||
|
self.mock_groups.append(_get_mock(group))
|
||||||
|
|
||||||
|
def search_mock_device_by_id(self, device_id):
|
||||||
|
"""Search a device by given id."""
|
||||||
|
for device in self.mock_devices:
|
||||||
|
if device.id == device_id:
|
||||||
|
return device
|
||||||
|
return None
|
||||||
|
|
||||||
|
def search_mock_group_by_id(self, group_id):
|
||||||
|
"""Search a group by given id."""
|
||||||
|
for group in self.mock_groups:
|
||||||
|
if group.id == group_id:
|
||||||
|
return group
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_async_home_mock(self):
|
||||||
|
"""
|
||||||
|
Create Mock for Async_Home. based on template to be used for testing.
|
||||||
|
|
||||||
|
It adds collections of mocked devices and groups to the home objects,
|
||||||
|
and sets reuired attributes.
|
||||||
|
"""
|
||||||
|
mock_home = Mock(
|
||||||
|
check_connection=self._connection,
|
||||||
|
id=HAPID,
|
||||||
|
connected=True,
|
||||||
|
dutyCycle=self.dutyCycle,
|
||||||
|
devices=self.mock_devices,
|
||||||
|
groups=self.mock_groups,
|
||||||
|
weather=self.weather,
|
||||||
|
location=self.location,
|
||||||
|
label="home label",
|
||||||
|
template=self,
|
||||||
|
spec=AsyncHome,
|
||||||
|
)
|
||||||
|
mock_home.name = ""
|
||||||
|
return mock_home
|
||||||
|
|
||||||
|
|
||||||
|
def _get_mock(instance):
|
||||||
|
"""Create a mock and copy instance attributes over mock."""
|
||||||
|
mock = Mock(spec=instance, wraps=instance)
|
||||||
|
mock.__dict__.update(instance.__dict__)
|
||||||
|
return mock
|
41
tests/components/homematicip_cloud/test_binary_sensors.py
Normal file
41
tests/components/homematicip_cloud/test_binary_sensors.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
"""Tests for HomematicIP Cloud lights."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from tests.components.homematicip_cloud.helper import (
|
||||||
|
async_manipulate_test_data,
|
||||||
|
get_and_check_entity_basics,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_hmip_sam(hass, default_mock_hap):
|
||||||
|
"""Test HomematicipLight."""
|
||||||
|
entity_id = "binary_sensor.garagentor"
|
||||||
|
entity_name = "Garagentor"
|
||||||
|
device_model = "HmIP-SAM"
|
||||||
|
|
||||||
|
ha_entity, hmip_device = get_and_check_entity_basics(
|
||||||
|
hass, default_mock_hap, entity_id, entity_name, device_model
|
||||||
|
)
|
||||||
|
|
||||||
|
assert ha_entity.state == "on"
|
||||||
|
assert ha_entity.attributes["acceleration_sensor_mode"] == "FLAT_DECT"
|
||||||
|
assert ha_entity.attributes["acceleration_sensor_neutral_position"] == "VERTICAL"
|
||||||
|
assert ha_entity.attributes["acceleration_sensor_sensitivity"] == "SENSOR_RANGE_4G"
|
||||||
|
assert ha_entity.attributes["acceleration_sensor_trigger_angle"] == 45
|
||||||
|
service_call_counter = len(hmip_device.mock_calls)
|
||||||
|
|
||||||
|
await async_manipulate_test_data(
|
||||||
|
hass, hmip_device, "accelerationSensorTriggered", False
|
||||||
|
)
|
||||||
|
ha_entity = hass.states.get(entity_id)
|
||||||
|
assert ha_entity.state == "off"
|
||||||
|
assert len(hmip_device.mock_calls) == service_call_counter + 1
|
||||||
|
|
||||||
|
await async_manipulate_test_data(
|
||||||
|
hass, hmip_device, "accelerationSensorTriggered", True
|
||||||
|
)
|
||||||
|
ha_entity = hass.states.get(entity_id)
|
||||||
|
assert ha_entity.state == "on"
|
||||||
|
assert len(hmip_device.mock_calls) == service_call_counter + 2
|
|
@ -1,8 +1,7 @@
|
||||||
"""Tests for HomematicIP Cloud config flow."""
|
"""Tests for HomematicIP Cloud config flow."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from homeassistant.components.homematicip_cloud import hap as hmipc
|
from homeassistant.components.homematicip_cloud import config_flow, const, hap as hmipc
|
||||||
from homeassistant.components.homematicip_cloud import config_flow, const
|
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, mock_coro
|
from tests.common import MockConfigEntry, mock_coro
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.homematicip_cloud import const, errors, hap as hmipc
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.components.homematicip_cloud import hap as hmipc
|
|
||||||
from homeassistant.components.homematicip_cloud import const, errors
|
|
||||||
from tests.common import mock_coro, mock_coro_func
|
from tests.common import mock_coro, mock_coro_func
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,8 +94,8 @@ async def test_hap_setup_connection_error():
|
||||||
), pytest.raises(ConfigEntryNotReady):
|
), pytest.raises(ConfigEntryNotReady):
|
||||||
await hap.async_setup()
|
await hap.async_setup()
|
||||||
|
|
||||||
assert len(hass.async_add_job.mock_calls) == 0
|
assert not hass.async_add_job.mock_calls
|
||||||
assert len(hass.config_entries.flow.async_init.mock_calls) == 0
|
assert not hass.config_entries.flow.async_init.mock_calls
|
||||||
|
|
||||||
|
|
||||||
async def test_hap_reset_unloads_entry_if_setup():
|
async def test_hap_reset_unloads_entry_if_setup():
|
||||||
|
@ -114,7 +114,7 @@ async def test_hap_reset_unloads_entry_if_setup():
|
||||||
assert await hap.async_setup() is True
|
assert await hap.async_setup() is True
|
||||||
|
|
||||||
assert hap.home is home
|
assert hap.home is home
|
||||||
assert len(hass.services.async_register.mock_calls) == 0
|
assert not hass.services.async_register.mock_calls
|
||||||
assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 8
|
assert len(hass.config_entries.async_forward_entry_setup.mock_calls) == 8
|
||||||
|
|
||||||
hass.config_entries.async_forward_entry_unload.return_value = mock_coro(True)
|
hass.config_entries.async_forward_entry_unload.return_value = mock_coro(True)
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
from homeassistant.components import homematicip_cloud as hmipc
|
from homeassistant.components import homematicip_cloud as hmipc
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.common import mock_coro, MockConfigEntry
|
from tests.common import MockConfigEntry, mock_coro
|
||||||
|
|
||||||
|
|
||||||
async def test_config_with_accesspoint_passed_to_config_entry(hass):
|
async def test_config_with_accesspoint_passed_to_config_entry(hass):
|
||||||
|
@ -53,7 +53,7 @@ async def test_config_already_registered_not_passed_to_config_entry(hass):
|
||||||
)
|
)
|
||||||
|
|
||||||
# No flow started
|
# No flow started
|
||||||
assert len(mock_config_entries.flow.mock_calls) == 0
|
assert not mock_config_entries.flow.mock_calls
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_entry_successful(hass):
|
async def test_setup_entry_successful(hass):
|
||||||
|
|
77
tests/components/homematicip_cloud/test_lights.py
Normal file
77
tests/components/homematicip_cloud/test_lights.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
"""Tests for HomematicIP Cloud lights."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from tests.components.homematicip_cloud.helper import (
|
||||||
|
async_manipulate_test_data,
|
||||||
|
get_and_check_entity_basics,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_hmip_light(hass, default_mock_hap):
|
||||||
|
"""Test HomematicipLight."""
|
||||||
|
entity_id = "light.treppe"
|
||||||
|
entity_name = "Treppe"
|
||||||
|
device_model = "HmIP-BSL"
|
||||||
|
|
||||||
|
ha_entity, hmip_device = get_and_check_entity_basics(
|
||||||
|
hass, default_mock_hap, entity_id, entity_name, device_model
|
||||||
|
)
|
||||||
|
|
||||||
|
assert ha_entity.state == "on"
|
||||||
|
|
||||||
|
service_call_counter = len(hmip_device.mock_calls)
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light", "turn_off", {"entity_id": entity_id}, blocking=True
|
||||||
|
)
|
||||||
|
assert len(hmip_device.mock_calls) == service_call_counter + 1
|
||||||
|
assert hmip_device.mock_calls[-1][0] == "turn_off"
|
||||||
|
await async_manipulate_test_data(hass, hmip_device, "on", False)
|
||||||
|
ha_entity = hass.states.get(entity_id)
|
||||||
|
assert ha_entity.state == "off"
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light", "turn_on", {"entity_id": entity_id}, blocking=True
|
||||||
|
)
|
||||||
|
assert len(hmip_device.mock_calls) == service_call_counter + 3
|
||||||
|
assert hmip_device.mock_calls[-1][0] == "turn_on"
|
||||||
|
await async_manipulate_test_data(hass, hmip_device, "on", True)
|
||||||
|
ha_entity = hass.states.get(entity_id)
|
||||||
|
assert ha_entity.state == "on"
|
||||||
|
|
||||||
|
|
||||||
|
# HomematicipLightMeasuring
|
||||||
|
# HomematicipDimmer
|
||||||
|
|
||||||
|
|
||||||
|
async def test_hmip_notification_light(hass, default_mock_hap):
|
||||||
|
"""Test HomematicipNotificationLight."""
|
||||||
|
entity_id = "light.treppe_top_notification"
|
||||||
|
entity_name = "Treppe Top Notification"
|
||||||
|
device_model = "HmIP-BSL"
|
||||||
|
|
||||||
|
ha_entity, hmip_device = get_and_check_entity_basics(
|
||||||
|
hass, default_mock_hap, entity_id, entity_name, device_model
|
||||||
|
)
|
||||||
|
|
||||||
|
assert ha_entity.state == "off"
|
||||||
|
service_call_counter = len(hmip_device.mock_calls)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light", "turn_on", {"entity_id": entity_id}, blocking=True
|
||||||
|
)
|
||||||
|
assert len(hmip_device.mock_calls) == service_call_counter + 1
|
||||||
|
assert hmip_device.mock_calls[-1][0] == "set_rgb_dim_level"
|
||||||
|
await async_manipulate_test_data(hass, hmip_device, "dimLevel", 100, 2)
|
||||||
|
ha_entity = hass.states.get(entity_id)
|
||||||
|
assert ha_entity.state == "on"
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light", "turn_off", {"entity_id": entity_id}, blocking=True
|
||||||
|
)
|
||||||
|
assert len(hmip_device.mock_calls) == service_call_counter + 3
|
||||||
|
assert hmip_device.mock_calls[-1][0] == "set_rgb_dim_level"
|
||||||
|
await async_manipulate_test_data(hass, hmip_device, "dimLevel", 0, 2)
|
||||||
|
ha_entity = hass.states.get(entity_id)
|
||||||
|
assert ha_entity.state == "off"
|
5341
tests/fixtures/homematicip_cloud.json
vendored
Normal file
5341
tests/fixtures/homematicip_cloud.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue