From 43595f7e17adb95db5a7287d583c40f00ba80fa1 Mon Sep 17 00:00:00 2001 From: Guido Schmitz Date: Fri, 1 Jul 2022 06:08:21 +0200 Subject: [PATCH] Add light tests for devolo_home_control (#74183) --- .coveragerc | 1 - tests/components/devolo_home_control/mocks.py | 41 +++++ .../devolo_home_control/test_light.py | 165 ++++++++++++++++++ 3 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 tests/components/devolo_home_control/test_light.py diff --git a/.coveragerc b/.coveragerc index f12d1ce88fa..21534333583 100644 --- a/.coveragerc +++ b/.coveragerc @@ -210,7 +210,6 @@ omit = homeassistant/components/denonavr/media_player.py homeassistant/components/denonavr/receiver.py homeassistant/components/deutsche_bahn/sensor.py - homeassistant/components/devolo_home_control/light.py homeassistant/components/devolo_home_control/sensor.py homeassistant/components/devolo_home_control/switch.py homeassistant/components/digital_ocean/* diff --git a/tests/components/devolo_home_control/mocks.py b/tests/components/devolo_home_control/mocks.py index e9dae0b70b1..129c4a377ef 100644 --- a/tests/components/devolo_home_control/mocks.py +++ b/tests/components/devolo_home_control/mocks.py @@ -8,6 +8,9 @@ from devolo_home_control_api.homecontrol import HomeControl from devolo_home_control_api.properties.binary_sensor_property import ( BinarySensorProperty, ) +from devolo_home_control_api.properties.binary_switch_property import ( + BinarySwitchProperty, +) from devolo_home_control_api.properties.multi_level_sensor_property import ( MultiLevelSensorProperty, ) @@ -31,6 +34,15 @@ class BinarySensorPropertyMock(BinarySensorProperty): self.state = False +class BinarySwitchPropertyMock(BinarySwitchProperty): + """devolo Home Control binary sensor mock.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize the mock.""" + self._logger = MagicMock() + self.element_uid = "Test" + + class MultiLevelSensorPropertyMock(MultiLevelSensorProperty): """devolo Home Control multi level sensor mock.""" @@ -134,6 +146,22 @@ class CoverMock(DeviceMock): } +class LightMock(DeviceMock): + """devolo Home Control light device mock.""" + + def __init__(self) -> None: + """Initialize the mock.""" + super().__init__() + self.binary_switch_property = {} + self.multi_level_switch_property = { + "devolo.Dimmer:Test": MultiLevelSwitchPropertyMock() + } + self.multi_level_switch_property["devolo.Dimmer:Test"].switch_type = "dimmer" + self.multi_level_switch_property[ + "devolo.Dimmer:Test" + ].element_uid = "devolo.Dimmer:Test" + + class RemoteControlMock(DeviceMock): """devolo Home Control remote control device mock.""" @@ -219,6 +247,19 @@ class HomeControlMockCover(HomeControlMock): self.publisher.unregister = MagicMock() +class HomeControlMockLight(HomeControlMock): + """devolo Home Control gateway mock with light devices.""" + + def __init__(self, **kwargs: Any) -> None: + """Initialize the mock.""" + super().__init__() + self.devices = { + "Test": LightMock(), + } + self.publisher = Publisher(self.devices.keys()) + self.publisher.unregister = MagicMock() + + class HomeControlMockRemoteControl(HomeControlMock): """devolo Home Control gateway mock with remote control device.""" diff --git a/tests/components/devolo_home_control/test_light.py b/tests/components/devolo_home_control/test_light.py new file mode 100644 index 00000000000..7b18b28a493 --- /dev/null +++ b/tests/components/devolo_home_control/test_light.py @@ -0,0 +1,165 @@ +"""Tests for the devolo Home Control light platform.""" +from unittest.mock import patch + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_MODE, + ATTR_SUPPORTED_COLOR_MODES, + DOMAIN, + ColorMode, +) +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + STATE_UNAVAILABLE, +) +from homeassistant.core import HomeAssistant + +from . import configure_integration +from .mocks import BinarySwitchPropertyMock, HomeControlMock, HomeControlMockLight + + +async def test_light_without_binary_sensor(hass: HomeAssistant): + """Test setup and state change of a light device that does not have an additional binary sensor.""" + entry = configure_integration(hass) + test_gateway = HomeControlMockLight() + with patch( + "homeassistant.components.devolo_home_control.HomeControl", + side_effect=[test_gateway, HomeControlMock()], + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.test") + assert state is not None + assert state.state == STATE_ON + assert state.attributes[ATTR_COLOR_MODE] == ColorMode.BRIGHTNESS + assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [ColorMode.BRIGHTNESS] + assert state.attributes[ATTR_BRIGHTNESS] == round( + test_gateway.devices["Test"] + .multi_level_switch_property["devolo.Dimmer:Test"] + .value + / 100 + * 255 + ) + + # Emulate websocket message: brightness changed + test_gateway.publisher.dispatch("Test", ("devolo.Dimmer:Test", 0.0)) + await hass.async_block_till_done() + state = hass.states.get(f"{DOMAIN}.test") + assert state.state == STATE_OFF + test_gateway.publisher.dispatch("Test", ("devolo.Dimmer:Test", 100.0)) + await hass.async_block_till_done() + state = hass.states.get(f"{DOMAIN}.test") + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 255 + + # Test setting brightness + with patch( + "devolo_home_control_api.properties.multi_level_switch_property.MultiLevelSwitchProperty.set" + ) as set_value: + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: f"{DOMAIN}.test"}, + blocking=True, + ) # In reality, this leads to a websocket message like already tested above + set_value.assert_called_once_with(100) + + set_value.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: f"{DOMAIN}.test"}, + blocking=True, + ) # In reality, this leads to a websocket message like already tested above + set_value.assert_called_once_with(0) + + set_value.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: f"{DOMAIN}.test", ATTR_BRIGHTNESS: 50}, + blocking=True, + ) # In reality, this leads to a websocket message like already tested above + set_value.assert_called_once_with(round(50 / 255 * 100)) + + # Emulate websocket message: device went offline + test_gateway.devices["Test"].status = 1 + test_gateway.publisher.dispatch("Test", ("Status", False, "status")) + await hass.async_block_till_done() + assert hass.states.get(f"{DOMAIN}.test").state == STATE_UNAVAILABLE + + +async def test_light_with_binary_sensor(hass: HomeAssistant): + """Test setup and state change of a light device that has an additional binary sensor.""" + entry = configure_integration(hass) + test_gateway = HomeControlMockLight() + test_gateway.devices["Test"].binary_switch_property = { + "devolo.BinarySwitch:Test": BinarySwitchPropertyMock() + } + with patch( + "homeassistant.components.devolo_home_control.HomeControl", + side_effect=[test_gateway, HomeControlMock()], + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.test") + assert state is not None + assert state.state == STATE_ON + + # Emulate websocket message: brightness changed + test_gateway.publisher.dispatch("Test", ("devolo.Dimmer:Test", 0.0)) + await hass.async_block_till_done() + state = hass.states.get(f"{DOMAIN}.test") + assert state.state == STATE_OFF + test_gateway.publisher.dispatch("Test", ("devolo.Dimmer:Test", 100.0)) + await hass.async_block_till_done() + state = hass.states.get(f"{DOMAIN}.test") + assert state.state == STATE_ON + assert state.attributes[ATTR_BRIGHTNESS] == 255 + + # Test setting brightness + with patch( + "devolo_home_control_api.properties.binary_switch_property.BinarySwitchProperty.set" + ) as set_value: + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: f"{DOMAIN}.test"}, + blocking=True, + ) # In reality, this leads to a websocket message like already tested above + set_value.assert_called_once_with(True) + + set_value.reset_mock() + await hass.services.async_call( + DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: f"{DOMAIN}.test"}, + blocking=True, + ) # In reality, this leads to a websocket message like already tested above + set_value.assert_called_once_with(False) + + +async def test_remove_from_hass(hass: HomeAssistant): + """Test removing entity.""" + entry = configure_integration(hass) + test_gateway = HomeControlMockLight() + with patch( + "homeassistant.components.devolo_home_control.HomeControl", + side_effect=[test_gateway, HomeControlMock()], + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(f"{DOMAIN}.test") + assert state is not None + await hass.config_entries.async_remove(entry.entry_id) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 0 + assert test_gateway.publisher.unregister.call_count == 1