From f979eca83ac364e4c5a334dcc41efd2974dbea6d Mon Sep 17 00:00:00 2001 From: SukramJ Date: Sat, 12 Oct 2019 21:45:11 +0200 Subject: [PATCH] Add test to Homematic IP Cloud climate (#27472) --- .../components/homematicip_cloud/hap.py | 1 + tests/components/homematicip_cloud/helper.py | 21 +- .../homematicip_cloud/test_climate.py | 230 ++++++++++++++++++ .../homematicip_cloud/test_device.py | 111 +++++++++ 4 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 tests/components/homematicip_cloud/test_climate.py create mode 100644 tests/components/homematicip_cloud/test_device.py diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py index 22ab1fd617c..f6727f91c7e 100644 --- a/homeassistant/components/homematicip_cloud/hap.py +++ b/homeassistant/components/homematicip_cloud/hap.py @@ -220,6 +220,7 @@ class HomematicipHAP: await self.hass.config_entries.async_forward_entry_unload( self.config_entry, component ) + self.hmip_device_by_entity_id = {} return True async def get_hap( diff --git a/tests/components/homematicip_cloud/helper.py b/tests/components/homematicip_cloud/helper.py index b5e41a6ae86..e5c5c4569d7 100644 --- a/tests/components/homematicip_cloud/helper.py +++ b/tests/components/homematicip_cloud/helper.py @@ -35,6 +35,7 @@ def get_and_check_entity_basics( assert ha_state.name == entity_name hmip_device = default_mock_hap.hmip_device_by_entity_id.get(entity_id) + if hmip_device: if isinstance(hmip_device, AsyncDevice): assert ha_state.attributes[ATTR_IS_GROUP] is False @@ -85,14 +86,20 @@ class HomeTemplate(Home): super().__init__(connection=connection) self.label = "Access Point" self.model_type = "HmIP-HAP" + self.init_json_state = None 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() + self.init_json_state = json.loads(load_fixture(HOME_JSON), encoding="UTF-8") + self.update_home(json_state=self.init_json_state, clearConfig=True) return self + def update_home(self, json_state, clearConfig: bool = False): + """Update home and ensure that mocks are created.""" + result = super().update_home(json_state, clearConfig) + self._generate_mocks() + return result + def _generate_mocks(self): """Generate mocks for groups and devices.""" mock_devices = [] @@ -105,6 +112,10 @@ class HomeTemplate(Home): mock_groups.append(_get_mock(group)) self.groups = mock_groups + def download_configuration(self): + """Return the initial json config.""" + return self.init_json_state + def get_async_home_mock(self): """ Create Mock for Async_Home. based on template to be used for testing. @@ -123,6 +134,10 @@ class HomeTemplate(Home): def _get_mock(instance): """Create a mock and copy instance attributes over mock.""" + if isinstance(instance, Mock): + instance.__dict__.update(instance._mock_wraps.__dict__) # pylint: disable=W0212 + return instance + mock = Mock(spec=instance, wraps=instance) mock.__dict__.update(instance.__dict__) return mock diff --git a/tests/components/homematicip_cloud/test_climate.py b/tests/components/homematicip_cloud/test_climate.py new file mode 100644 index 00000000000..8f8a681fad8 --- /dev/null +++ b/tests/components/homematicip_cloud/test_climate.py @@ -0,0 +1,230 @@ +"""Tests for HomematicIP Cloud climate.""" +import datetime + +from homematicip.base.enums import AbsenceType +from homematicip.functionalHomes import IndoorClimateHome + +from homeassistant.components.climate.const import ( + ATTR_CURRENT_TEMPERATURE, + ATTR_PRESET_MODE, + ATTR_PRESET_MODES, + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + PRESET_AWAY, + PRESET_BOOST, + PRESET_ECO, + PRESET_NONE, +) + +from .helper import HAPID, async_manipulate_test_data, get_and_check_entity_basics + + +async def test_hmip_heating_group(hass, default_mock_hap): + """Test HomematicipHeatingGroup.""" + entity_id = "climate.badezimmer" + entity_name = "Badezimmer" + device_model = None + + ha_state, hmip_device = get_and_check_entity_basics( + hass, default_mock_hap, entity_id, entity_name, device_model + ) + + assert ha_state.state == HVAC_MODE_AUTO + assert ha_state.attributes["current_temperature"] == 23.8 + assert ha_state.attributes["min_temp"] == 5.0 + assert ha_state.attributes["max_temp"] == 30.0 + assert ha_state.attributes["temperature"] == 5.0 + assert ha_state.attributes["current_humidity"] == 47 + assert ha_state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + assert ha_state.attributes[ATTR_PRESET_MODES] == [PRESET_NONE, PRESET_BOOST] + + service_call_counter = len(hmip_device.mock_calls) + + await hass.services.async_call( + "climate", + "set_temperature", + {"entity_id": entity_id, "temperature": 22.5}, + blocking=True, + ) + assert len(hmip_device.mock_calls) == service_call_counter + 1 + assert hmip_device.mock_calls[-1][0] == "set_point_temperature" + assert hmip_device.mock_calls[-1][1] == (22.5,) + await async_manipulate_test_data(hass, hmip_device, "actualTemperature", 22.5) + ha_state = hass.states.get(entity_id) + assert ha_state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.5 + + await hass.services.async_call( + "climate", + "set_hvac_mode", + {"entity_id": entity_id, "hvac_mode": HVAC_MODE_HEAT}, + blocking=True, + ) + assert len(hmip_device.mock_calls) == service_call_counter + 3 + assert hmip_device.mock_calls[-1][0] == "set_control_mode" + assert hmip_device.mock_calls[-1][1] == ("MANUAL",) + await async_manipulate_test_data(hass, hmip_device, "controlMode", "MANUAL") + ha_state = hass.states.get(entity_id) + assert ha_state.state == HVAC_MODE_HEAT + + await hass.services.async_call( + "climate", + "set_hvac_mode", + {"entity_id": entity_id, "hvac_mode": HVAC_MODE_AUTO}, + blocking=True, + ) + assert len(hmip_device.mock_calls) == service_call_counter + 5 + assert hmip_device.mock_calls[-1][0] == "set_control_mode" + assert hmip_device.mock_calls[-1][1] == ("AUTOMATIC",) + await async_manipulate_test_data(hass, hmip_device, "controlMode", "AUTO") + ha_state = hass.states.get(entity_id) + assert ha_state.state == HVAC_MODE_AUTO + + await hass.services.async_call( + "climate", + "set_preset_mode", + {"entity_id": entity_id, "preset_mode": PRESET_BOOST}, + blocking=True, + ) + assert len(hmip_device.mock_calls) == service_call_counter + 7 + assert hmip_device.mock_calls[-1][0] == "set_boost" + assert hmip_device.mock_calls[-1][1] == () + await async_manipulate_test_data(hass, hmip_device, "boostMode", True) + ha_state = hass.states.get(entity_id) + assert ha_state.attributes[ATTR_PRESET_MODE] == PRESET_BOOST + + await hass.services.async_call( + "climate", + "set_preset_mode", + {"entity_id": entity_id, "preset_mode": PRESET_NONE}, + blocking=True, + ) + assert len(hmip_device.mock_calls) == service_call_counter + 9 + assert hmip_device.mock_calls[-1][0] == "set_boost" + assert hmip_device.mock_calls[-1][1] == (False,) + await async_manipulate_test_data(hass, hmip_device, "boostMode", False) + ha_state = hass.states.get(entity_id) + assert ha_state.attributes[ATTR_PRESET_MODE] == PRESET_NONE + + # Not required for hmip, but a posiblity to send no temperature. + await hass.services.async_call( + "climate", + "set_temperature", + {"entity_id": entity_id, "target_temp_low": 10, "target_temp_high": 10}, + blocking=True, + ) + # No new service call should be in mock_calls. + assert len(hmip_device.mock_calls) == service_call_counter + 10 + # Only fire event from last async_manipulate_test_data available. + assert hmip_device.mock_calls[-1][0] == "fire_update_event" + + await async_manipulate_test_data(hass, hmip_device, "controlMode", "ECO") + await async_manipulate_test_data( + hass, + default_mock_hap.home.get_functionalHome(IndoorClimateHome), + "absenceType", + AbsenceType.VACATION, + fire_device=hmip_device, + ) + ha_state = hass.states.get(entity_id) + assert ha_state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY + + await async_manipulate_test_data(hass, hmip_device, "controlMode", "ECO") + await async_manipulate_test_data( + hass, + default_mock_hap.home.get_functionalHome(IndoorClimateHome), + "absenceType", + AbsenceType.PERIOD, + fire_device=hmip_device, + ) + ha_state = hass.states.get(entity_id) + assert ha_state.attributes[ATTR_PRESET_MODE] == PRESET_ECO + + +async def test_hmip_climate_services(hass, mock_hap_with_service): + """Test HomematicipHeatingGroup.""" + + home = mock_hap_with_service.home + + await hass.services.async_call( + "homematicip_cloud", + "activate_eco_mode_with_duration", + {"duration": 60, "accesspoint_id": HAPID}, + blocking=True, + ) + assert home.mock_calls[-1][0] == "activate_absence_with_duration" + assert home.mock_calls[-1][1] == (60,) + + await hass.services.async_call( + "homematicip_cloud", + "activate_eco_mode_with_duration", + {"duration": 60}, + blocking=True, + ) + assert home.mock_calls[-1][0] == "activate_absence_with_duration" + assert home.mock_calls[-1][1] == (60,) + + await hass.services.async_call( + "homematicip_cloud", + "activate_eco_mode_with_period", + {"endtime": "2019-02-17 14:00", "accesspoint_id": HAPID}, + blocking=True, + ) + assert home.mock_calls[-1][0] == "activate_absence_with_period" + assert home.mock_calls[-1][1] == (datetime.datetime(2019, 2, 17, 14, 0),) + + await hass.services.async_call( + "homematicip_cloud", + "activate_eco_mode_with_period", + {"endtime": "2019-02-17 14:00"}, + blocking=True, + ) + assert home.mock_calls[-1][0] == "activate_absence_with_period" + assert home.mock_calls[-1][1] == (datetime.datetime(2019, 2, 17, 14, 0),) + + await hass.services.async_call( + "homematicip_cloud", + "activate_vacation", + {"endtime": "2019-02-17 14:00", "temperature": 18.5, "accesspoint_id": HAPID}, + blocking=True, + ) + assert home.mock_calls[-1][0] == "activate_vacation" + assert home.mock_calls[-1][1] == (datetime.datetime(2019, 2, 17, 14, 0), 18.5) + + await hass.services.async_call( + "homematicip_cloud", + "activate_vacation", + {"endtime": "2019-02-17 14:00", "temperature": 18.5}, + blocking=True, + ) + assert home.mock_calls[-1][0] == "activate_vacation" + assert home.mock_calls[-1][1] == (datetime.datetime(2019, 2, 17, 14, 0), 18.5) + + await hass.services.async_call( + "homematicip_cloud", + "deactivate_eco_mode", + {"accesspoint_id": HAPID}, + blocking=True, + ) + assert home.mock_calls[-1][0] == "deactivate_absence" + assert home.mock_calls[-1][1] == () + + await hass.services.async_call( + "homematicip_cloud", "deactivate_eco_mode", blocking=True + ) + assert home.mock_calls[-1][0] == "deactivate_absence" + assert home.mock_calls[-1][1] == () + + await hass.services.async_call( + "homematicip_cloud", + "deactivate_vacation", + {"accesspoint_id": HAPID}, + blocking=True, + ) + assert home.mock_calls[-1][0] == "deactivate_vacation" + assert home.mock_calls[-1][1] == () + + await hass.services.async_call( + "homematicip_cloud", "deactivate_vacation", blocking=True + ) + assert home.mock_calls[-1][0] == "deactivate_vacation" + assert home.mock_calls[-1][1] == () diff --git a/tests/components/homematicip_cloud/test_device.py b/tests/components/homematicip_cloud/test_device.py new file mode 100644 index 00000000000..81c35f8e2a9 --- /dev/null +++ b/tests/components/homematicip_cloud/test_device.py @@ -0,0 +1,111 @@ +"""Common tests for HomematicIP devices.""" +from homeassistant.const import STATE_ON, STATE_UNAVAILABLE +from homeassistant.helpers import device_registry as dr, entity_registry as er + +from .helper import async_manipulate_test_data, get_and_check_entity_basics + + +async def test_hmip_remove_device(hass, default_mock_hap): + """Test Remove of hmip device.""" + entity_id = "light.treppe" + entity_name = "Treppe" + device_model = "HmIP-BSL" + + ha_state, hmip_device = get_and_check_entity_basics( + hass, default_mock_hap, entity_id, entity_name, device_model + ) + + assert ha_state.state == STATE_ON + assert hmip_device + + device_registry = await dr.async_get_registry(hass) + entity_registry = await er.async_get_registry(hass) + + pre_device_count = len(device_registry.devices) + pre_entity_count = len(entity_registry.entities) + pre_mapping_count = len(default_mock_hap.hmip_device_by_entity_id) + + hmip_device.fire_remove_event() + + await hass.async_block_till_done() + + assert len(device_registry.devices) == pre_device_count - 1 + assert len(entity_registry.entities) == pre_entity_count - 3 + assert len(default_mock_hap.hmip_device_by_entity_id) == pre_mapping_count - 3 + + +async def test_hmip_remove_group(hass, default_mock_hap): + """Test Remove of hmip group.""" + entity_id = "switch.strom_group" + entity_name = "Strom Group" + device_model = None + + ha_state, hmip_device = get_and_check_entity_basics( + hass, default_mock_hap, entity_id, entity_name, device_model + ) + + assert ha_state.state == STATE_ON + assert hmip_device + + device_registry = await dr.async_get_registry(hass) + entity_registry = await er.async_get_registry(hass) + + pre_device_count = len(device_registry.devices) + pre_entity_count = len(entity_registry.entities) + pre_mapping_count = len(default_mock_hap.hmip_device_by_entity_id) + + hmip_device.fire_remove_event() + + await hass.async_block_till_done() + + assert len(device_registry.devices) == pre_device_count + assert len(entity_registry.entities) == pre_entity_count - 1 + assert len(default_mock_hap.hmip_device_by_entity_id) == pre_mapping_count - 1 + + +async def test_all_devices_unavailable_when_hap_not_connected(hass, default_mock_hap): + """Test make all devices unavaulable when hap is not connected.""" + entity_id = "light.treppe" + entity_name = "Treppe" + device_model = "HmIP-BSL" + + ha_state, hmip_device = get_and_check_entity_basics( + hass, default_mock_hap, entity_id, entity_name, device_model + ) + + assert ha_state.state == STATE_ON + assert hmip_device + + assert default_mock_hap.home.connected + + await async_manipulate_test_data(hass, default_mock_hap.home, "connected", False) + + ha_state = hass.states.get(entity_id) + assert ha_state.state == STATE_UNAVAILABLE + + +async def test_hap_reconnected(hass, default_mock_hap): + """Test reconnect hap.""" + entity_id = "light.treppe" + entity_name = "Treppe" + device_model = "HmIP-BSL" + + ha_state, hmip_device = get_and_check_entity_basics( + hass, default_mock_hap, entity_id, entity_name, device_model + ) + + assert ha_state.state == STATE_ON + assert hmip_device + + assert default_mock_hap.home.connected + + await async_manipulate_test_data(hass, default_mock_hap.home, "connected", False) + + ha_state = hass.states.get(entity_id) + assert ha_state.state == STATE_UNAVAILABLE + + default_mock_hap._accesspoint_connected = False # pylint: disable=W0212 + await async_manipulate_test_data(hass, default_mock_hap.home, "connected", True) + await hass.async_block_till_done() + ha_state = hass.states.get(entity_id) + assert ha_state.state == STATE_ON