From c929fbeea333b6c5f473aa0d5844ee7a3b497744 Mon Sep 17 00:00:00 2001 From: Xiaonan Shen Date: Tue, 19 Jan 2021 17:12:38 +0800 Subject: [PATCH] Rewrite dyson fan test (#45295) * Improve device fixture to take parameter * Remove unused code in dyson/fan * Rewrite dyson fan test --- homeassistant/components/dyson/fan.py | 28 +- tests/components/dyson/conftest.py | 6 +- tests/components/dyson/test_fan.py | 1206 +++++++------------------ tests/components/dyson/test_vacuum.py | 14 +- 4 files changed, 357 insertions(+), 897 deletions(-) diff --git a/homeassistant/components/dyson/fan.py b/homeassistant/components/dyson/fan.py index 62d857affa9..d92fdd9ed08 100644 --- a/homeassistant/components/dyson/fan.py +++ b/homeassistant/components/dyson/fan.py @@ -237,28 +237,19 @@ class DysonPureCoolLinkEntity(DysonFanEntity): @property def oscillating(self): """Return the oscillation state.""" - return self._device.state and self._device.state.oscillation == "ON" + return self._device.state.oscillation == "ON" @property def is_on(self): """Return true if the entity is on.""" - if self._device.state: - return self._device.state.fan_mode in ["FAN", "AUTO"] - return False + return self._device.state.fan_mode in ["FAN", "AUTO"] @property def speed(self) -> str: """Return the current speed.""" - if self._device.state: - if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value: - return self._device.state.speed - return int(self._device.state.speed) - return None - - @property - def current_direction(self): - """Return direction of the fan [forward, reverse].""" - return None + if self._device.state.speed == FanSpeed.FAN_SPEED_AUTO.value: + return self._device.state.speed + return int(self._device.state.speed) def set_night_mode(self, night_mode: bool) -> None: """Turn fan in night mode.""" @@ -414,9 +405,7 @@ class DysonPureCoolEntity(DysonFanEntity): @property def is_on(self): """Return true if the entity is on.""" - if self._device.state: - return self._device.state.fan_power == "ON" - return False + return self._device.state.fan_power == "ON" @property def speed(self): @@ -503,11 +492,6 @@ class DysonPureCoolEntity(DysonFanEntity): int(FanSpeed.FAN_SPEED_10.value), ] - @property - def device_serial(self): - """Return fan's serial number.""" - return self._device.serial - @property def supported_features(self) -> int: """Flag supported features.""" diff --git a/tests/components/dyson/conftest.py b/tests/components/dyson/conftest.py index 9edef6bae0c..eef77d752db 100644 --- a/tests/components/dyson/conftest.py +++ b/tests/components/dyson/conftest.py @@ -18,8 +18,12 @@ BASE_PATH = "homeassistant.components.dyson" @pytest.fixture async def device(hass: HomeAssistant, request) -> DysonDevice: """Fixture to provide Dyson 360 Eye device.""" - device = request.module.get_device() platform = request.module.PLATFORM_DOMAIN + get_device = request.module.get_device + if hasattr(request, "param"): + device = get_device(request.param) + else: + device = get_device() with patch(f"{BASE_PATH}.DysonAccount.login", return_value=True), patch( f"{BASE_PATH}.DysonAccount.devices", return_value=[device] ), patch(f"{BASE_PATH}.DYSON_PLATFORMS", [platform]): diff --git a/tests/components/dyson/test_fan.py b/tests/components/dyson/test_fan.py index ee3a87917e9..8fa84fab786 100644 --- a/tests/components/dyson/test_fan.py +++ b/tests/components/dyson/test_fan.py @@ -1,894 +1,370 @@ """Test the Dyson fan component.""" -import json -import unittest -from unittest import mock -from unittest.mock import patch +from typing import Type -from libpurecool.const import FanMode, FanSpeed, NightMode, Oscillation -from libpurecool.dyson_pure_cool import DysonPureCool -from libpurecool.dyson_pure_cool_link import DysonPureCoolLink +from libpurecool.const import SLEEP_TIMER_OFF, FanMode, FanSpeed, NightMode, Oscillation +from libpurecool.dyson_pure_cool import DysonPureCool, DysonPureCoolLink from libpurecool.dyson_pure_state import DysonPureCoolState from libpurecool.dyson_pure_state_v2 import DysonPureCoolV2State +import pytest -from homeassistant.components import dyson as dyson_parent -from homeassistant.components.dyson import DYSON_DEVICES -import homeassistant.components.dyson.fan as dyson +from homeassistant.components.dyson import DOMAIN +from homeassistant.components.dyson.fan import ( + ATTR_ANGLE_HIGH, + ATTR_ANGLE_LOW, + ATTR_AUTO_MODE, + ATTR_CARBON_FILTER, + ATTR_DYSON_SPEED, + ATTR_DYSON_SPEED_LIST, + ATTR_FLOW_DIRECTION_FRONT, + ATTR_HEPA_FILTER, + ATTR_NIGHT_MODE, + ATTR_TIMER, + SERVICE_SET_ANGLE, + SERVICE_SET_AUTO_MODE, + SERVICE_SET_DYSON_SPEED, + SERVICE_SET_FLOW_DIRECTION_FRONT, + SERVICE_SET_NIGHT_MODE, + SERVICE_SET_TIMER, + SPEED_LOW, +) from homeassistant.components.fan import ( ATTR_OSCILLATING, ATTR_SPEED, - DOMAIN, + ATTR_SPEED_LIST, + DOMAIN as PLATFORM_DOMAIN, SERVICE_OSCILLATE, + SERVICE_SET_SPEED, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, SPEED_HIGH, - SPEED_LOW, SPEED_MEDIUM, + SUPPORT_OSCILLATE, + SUPPORT_SET_SPEED, ) -from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON -from homeassistant.helpers import discovery -from homeassistant.setup import async_setup_component - -from .common import load_mock_device - -from tests.common import get_test_home_assistant - - -class MockDysonState(DysonPureCoolState): - """Mock Dyson state.""" - - def __init__(self): - """Create new Mock Dyson State.""" - pass - - def __repr__(self): - """Mock repr because original one fails since constructor not called.""" - return "" - - -def _get_dyson_purecool_device(): - """Return a valid device as provided by the Dyson web services.""" - device = mock.Mock(spec=DysonPureCool) - load_mock_device(device) - device.name = "Living room" - return device - - -def _get_dyson_purecoollink_device(): - """Return a valid device as provided by the Dyson web services.""" - device = mock.Mock(spec=DysonPureCoolLink) - load_mock_device(device) - device.name = "Living room" - device.state.oscillation = "ON" - device.state.fan_mode = "FAN" - device.state.speed = FanSpeed.FAN_SPEED_AUTO.value - return device - - -def _get_supported_speeds(): - return [ - int(FanSpeed.FAN_SPEED_1.value), - int(FanSpeed.FAN_SPEED_2.value), - int(FanSpeed.FAN_SPEED_3.value), - int(FanSpeed.FAN_SPEED_4.value), - int(FanSpeed.FAN_SPEED_5.value), - int(FanSpeed.FAN_SPEED_6.value), - int(FanSpeed.FAN_SPEED_7.value), - int(FanSpeed.FAN_SPEED_8.value), - int(FanSpeed.FAN_SPEED_9.value), - int(FanSpeed.FAN_SPEED_10.value), - ] - - -def _get_config(): - """Return a config dictionary.""" - return { - dyson_parent.DOMAIN: { - dyson_parent.CONF_USERNAME: "email", - dyson_parent.CONF_PASSWORD: "password", - dyson_parent.CONF_LANGUAGE: "GB", - dyson_parent.CONF_DEVICES: [ - {"device_id": "XX-XXXXX-XX", "device_ip": "192.168.0.1"} - ], - } - } - - -def _get_device_with_no_state(): - """Return a device with no state.""" - device = mock.Mock() - device.name = "Device_name" - device.state = None - return device - - -def _get_device_off(): - """Return a device with state off.""" - device = mock.Mock() - device.name = "Device_name" - device.state = mock.Mock() - device.state.fan_mode = "OFF" - device.state.night_mode = "ON" - device.state.speed = "0004" - return device - - -def _get_device_auto(): - """Return a device with state auto.""" - device = mock.Mock() - device.name = "Device_name" - device.state = mock.Mock() - device.state.fan_mode = "AUTO" - device.state.night_mode = "ON" - device.state.speed = "AUTO" - return device - - -def _get_device_on(): - """Return a valid state on.""" - device = mock.Mock(spec=DysonPureCoolLink) - device.name = "Device_name" - device.state = mock.Mock() - device.state.fan_mode = "FAN" - device.state.fan_state = "FAN" - device.state.oscillation = "ON" - device.state.night_mode = "OFF" - device.state.speed = "0001" - return device - - -class DysonSetupTest(unittest.TestCase): - """Dyson component setup tests.""" - - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.tear_down_cleanup) - - def tear_down_cleanup(self): - """Stop everything that was started.""" - self.hass.stop() - - def test_setup_component_with_no_devices(self): - """Test setup component with no devices.""" - self.hass.data[dyson.DYSON_DEVICES] = [] - add_entities = mock.MagicMock() - dyson.setup_platform(self.hass, None, add_entities, mock.Mock()) - add_entities.assert_called_with([]) - - def test_setup_component(self): - """Test setup component with devices.""" - - def _add_device(devices): - assert len(devices) == 2 - assert devices[0].name == "Device_name" - - device_fan = _get_device_on() - device_purecool_fan = _get_dyson_purecool_device() - device_non_fan = _get_device_off() - - self.hass.data[dyson.DYSON_DEVICES] = [ - device_fan, - device_purecool_fan, - device_non_fan, - ] - dyson.setup_platform(self.hass, None, _add_device) - - -class DysonTest(unittest.TestCase): - """Dyson fan component test class.""" - - def setUp(self): # pylint: disable=invalid-name - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.addCleanup(self.tear_down_cleanup) - - def tear_down_cleanup(self): - """Stop everything that was started.""" - self.hass.stop() - - def test_dyson_set_speed(self): - """Test set fan speed.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.should_poll - component.set_speed("1") - set_config = device.set_configuration - set_config.assert_called_with( - fan_mode=FanMode.FAN, fan_speed=FanSpeed.FAN_SPEED_1 - ) - - component.set_speed("AUTO") - set_config = device.set_configuration - set_config.assert_called_with(fan_mode=FanMode.AUTO) - - def test_dyson_turn_on(self): - """Test turn on fan.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.should_poll - component.turn_on() - set_config = device.set_configuration - set_config.assert_called_with(fan_mode=FanMode.FAN) - - def test_dyson_turn_night_mode(self): - """Test turn on fan with night mode.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.should_poll - component.set_night_mode(True) - set_config = device.set_configuration - set_config.assert_called_with(night_mode=NightMode.NIGHT_MODE_ON) - - component.set_night_mode(False) - set_config = device.set_configuration - set_config.assert_called_with(night_mode=NightMode.NIGHT_MODE_OFF) - - def test_is_night_mode(self): - """Test night mode.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.night_mode - - device = _get_device_off() - component = dyson.DysonPureCoolLinkEntity(device) - assert component.night_mode - - def test_dyson_turn_auto_mode(self): - """Test turn on/off fan with auto mode.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.should_poll - component.set_auto_mode(True) - set_config = device.set_configuration - set_config.assert_called_with(fan_mode=FanMode.AUTO) - - component.set_auto_mode(False) - set_config = device.set_configuration - set_config.assert_called_with(fan_mode=FanMode.FAN) - - def test_is_auto_mode(self): - """Test auto mode.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.auto_mode - - device = _get_device_auto() - component = dyson.DysonPureCoolLinkEntity(device) - assert component.auto_mode - - def test_dyson_turn_on_speed(self): - """Test turn on fan with specified speed.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.should_poll - component.turn_on("1") - set_config = device.set_configuration - set_config.assert_called_with( - fan_mode=FanMode.FAN, fan_speed=FanSpeed.FAN_SPEED_1 - ) - - component.turn_on("AUTO") - set_config = device.set_configuration - set_config.assert_called_with(fan_mode=FanMode.AUTO) - - def test_dyson_turn_off(self): - """Test turn off fan.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.should_poll - component.turn_off() - set_config = device.set_configuration - set_config.assert_called_with(fan_mode=FanMode.OFF) - - def test_dyson_oscillate_off(self): - """Test turn off oscillation.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - component.oscillate(False) - set_config = device.set_configuration - set_config.assert_called_with(oscillation=Oscillation.OSCILLATION_OFF) - - def test_dyson_oscillate_on(self): - """Test turn on oscillation.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - component.oscillate(True) - set_config = device.set_configuration - set_config.assert_called_with(oscillation=Oscillation.OSCILLATION_ON) - - def test_dyson_oscillate_value_on(self): - """Test get oscillation value on.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert component.oscillating - - def test_dyson_oscillate_value_off(self): - """Test get oscillation value off.""" - device = _get_device_off() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.oscillating - - def test_dyson_on(self): - """Test device is on.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert component.is_on - - def test_dyson_off(self): - """Test device is off.""" - device = _get_device_off() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.is_on - - device = _get_device_with_no_state() - component = dyson.DysonPureCoolLinkEntity(device) - assert not component.is_on - - def test_dyson_get_speed(self): - """Test get device speed.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert component.speed == 1 - - device = _get_device_off() - component = dyson.DysonPureCoolLinkEntity(device) - assert component.speed == 4 - - device = _get_device_with_no_state() - component = dyson.DysonPureCoolLinkEntity(device) - assert component.speed is None - - device = _get_device_auto() - component = dyson.DysonPureCoolLinkEntity(device) - assert component.speed == "AUTO" - - def test_dyson_get_direction(self): - """Test get device direction.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert component.current_direction is None - - def test_dyson_get_speed_list(self): - """Test get speeds list.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert len(component.speed_list) == 11 - - def test_dyson_supported_features(self): - """Test supported features.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - assert component.supported_features == 3 - - def test_on_message(self): - """Test when message is received.""" - device = _get_device_on() - component = dyson.DysonPureCoolLinkEntity(device) - component.entity_id = "entity_id" - component.schedule_update_ha_state = mock.Mock() - component.on_message(MockDysonState()) - component.schedule_update_ha_state.assert_called_with() - - def test_service_set_night_mode(self): - """Test set night mode service.""" - dyson_device = mock.MagicMock() - self.hass.data[DYSON_DEVICES] = [] - dyson_device.entity_id = "fan.living_room" - self.hass.data[dyson.DYSON_FAN_DEVICES] = [dyson_device] - dyson.setup_platform(self.hass, None, mock.MagicMock(), mock.MagicMock()) - - self.hass.services.call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_NIGHT_MODE, - {"entity_id": "fan.bed_room", "night_mode": True}, - True, - ) - assert dyson_device.set_night_mode.call_count == 0 - - self.hass.services.call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_NIGHT_MODE, - {"entity_id": "fan.living_room", "night_mode": True}, - True, - ) - dyson_device.set_night_mode.assert_called_with(True) - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecoollink_device()], +from homeassistant.const import ( + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + STATE_OFF, + STATE_ON, ) -async def test_purecoollink_attributes(devices, login, hass): - """Test state attributes.""" - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - fan_state = hass.states.get("fan.living_room") - attributes = fan_state.attributes +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry - assert fan_state.state == "on" - assert attributes[dyson.ATTR_NIGHT_MODE] is False - assert attributes[ATTR_SPEED] == FanSpeed.FAN_SPEED_AUTO.value +from .common import ENTITY_NAME, NAME, SERIAL, async_update_device, get_basic_device + +ENTITY_ID = f"{PLATFORM_DOMAIN}.{ENTITY_NAME}" + + +@callback +def get_device(spec: Type[DysonPureCoolLink]) -> DysonPureCoolLink: + """Return a Dyson fan device.""" + device = get_basic_device(spec) + if spec == DysonPureCoolLink: + device.state.fan_mode = FanMode.FAN.value + device.state.speed = FanSpeed.FAN_SPEED_1.value + device.state.night_mode = "ON" + device.state.oscillation = "ON" + else: # DysonPureCool + device.state.fan_power = "ON" + device.state.speed = FanSpeed.FAN_SPEED_1.value + device.state.night_mode = "ON" + device.state.oscillation = "OION" + device.state.oscillation_angle_low = "0024" + device.state.oscillation_angle_high = "0254" + device.state.auto_mode = "OFF" + device.state.front_direction = "ON" + device.state.sleep_timer = SLEEP_TIMER_OFF + device.state.hepa_filter_state = "0100" + device.state.carbon_filter_state = "0100" + return device + + +@pytest.mark.parametrize("device", [DysonPureCoolLink], indirect=True) +async def test_state_purecoollink( + hass: HomeAssistant, device: DysonPureCoolLink +) -> None: + """Test the state of a PureCoolLink fan.""" + er = await entity_registry.async_get_registry(hass) + assert er.async_get(ENTITY_ID).unique_id == SERIAL + + state = hass.states.get(ENTITY_ID) + assert state.state == STATE_ON + assert state.name == NAME + attributes = state.attributes + assert attributes[ATTR_NIGHT_MODE] is True assert attributes[ATTR_OSCILLATING] is True - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_turn_on(devices, login, hass): - """Test turn on.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - await hass.services.async_call( - DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "fan.bed_room"}, True + assert attributes[ATTR_SPEED] == 1 + assert attributes[ATTR_SPEED_LIST] == [FanSpeed.FAN_SPEED_AUTO.value] + list( + range(1, 11) ) - assert device.turn_on.call_count == 0 + assert attributes[ATTR_AUTO_MODE] is False + assert attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_OSCILLATE | SUPPORT_SET_SPEED - await hass.services.async_call( - DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: "fan.living_room"}, True - ) - assert device.turn_on.call_count == 1 + device.state.fan_mode = FanMode.OFF.value + await async_update_device(hass, device, DysonPureCoolState) + state = hass.states.get(ENTITY_ID) + assert state.state == STATE_OFF - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_set_speed(devices, login, hass): - """Test set speed.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - await hass.services.async_call( - DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "fan.bed_room", ATTR_SPEED: SPEED_LOW}, - True, - ) - assert device.set_fan_speed.call_count == 0 - - await hass.services.async_call( - DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "fan.living_room", ATTR_SPEED: SPEED_LOW}, - True, - ) - device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_4) - - await hass.services.async_call( - DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "fan.living_room", ATTR_SPEED: SPEED_MEDIUM}, - True, - ) - device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_7) - - await hass.services.async_call( - DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "fan.living_room", ATTR_SPEED: SPEED_HIGH}, - True, - ) - device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_10) - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_turn_off(devices, login, hass): - """Test turn off.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - await hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "fan.bed_room"}, True - ) - assert device.turn_off.call_count == 0 - - await hass.services.async_call( - DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: "fan.living_room"}, True - ) - assert device.turn_off.call_count == 1 - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_set_dyson_speed(devices, login, hass): - """Test set exact dyson speed.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_DYSON_SPEED, - { - ATTR_ENTITY_ID: "fan.bed_room", - dyson.ATTR_DYSON_SPEED: int(FanSpeed.FAN_SPEED_2.value), - }, - True, - ) - assert device.set_fan_speed.call_count == 0 - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_DYSON_SPEED, - { - ATTR_ENTITY_ID: "fan.living_room", - dyson.ATTR_DYSON_SPEED: int(FanSpeed.FAN_SPEED_2.value), - }, - True, - ) - device.set_fan_speed.assert_called_with(FanSpeed.FAN_SPEED_2) - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_oscillate(devices, login, hass): - """Test set oscillation.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - await hass.services.async_call( - DOMAIN, - SERVICE_OSCILLATE, - {ATTR_ENTITY_ID: "fan.bed_room", ATTR_OSCILLATING: True}, - True, - ) - assert device.enable_oscillation.call_count == 0 - - await hass.services.async_call( - DOMAIN, - SERVICE_OSCILLATE, - {ATTR_ENTITY_ID: "fan.living_room", ATTR_OSCILLATING: True}, - True, - ) - assert device.enable_oscillation.call_count == 1 - - await hass.services.async_call( - DOMAIN, - SERVICE_OSCILLATE, - {ATTR_ENTITY_ID: "fan.living_room", ATTR_OSCILLATING: False}, - True, - ) - assert device.disable_oscillation.call_count == 1 - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_set_night_mode(devices, login, hass): - """Test set night mode.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - - await hass.async_block_till_done() - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_NIGHT_MODE, - {"entity_id": "fan.bed_room", "night_mode": True}, - True, - ) - assert device.enable_night_mode.call_count == 0 - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_NIGHT_MODE, - {"entity_id": "fan.living_room", "night_mode": True}, - True, - ) - assert device.enable_night_mode.call_count == 1 - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_NIGHT_MODE, - {"entity_id": "fan.living_room", "night_mode": False}, - True, - ) - assert device.disable_night_mode.call_count == 1 - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_set_auto_mode(devices, login, hass): - """Test set auto mode.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_AUTO_MODE, - {ATTR_ENTITY_ID: "fan.bed_room", dyson.ATTR_AUTO_MODE: True}, - True, - ) - assert device.enable_auto_mode.call_count == 0 - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_AUTO_MODE, - {ATTR_ENTITY_ID: "fan.living_room", dyson.ATTR_AUTO_MODE: True}, - True, - ) - assert device.enable_auto_mode.call_count == 1 - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_AUTO_MODE, - {ATTR_ENTITY_ID: "fan.living_room", dyson.ATTR_AUTO_MODE: False}, - True, - ) - assert device.disable_auto_mode.call_count == 1 - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_set_angle(devices, login, hass): - """Test set angle.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_ANGLE, - { - ATTR_ENTITY_ID: "fan.bed_room", - dyson.ATTR_ANGLE_LOW: 90, - dyson.ATTR_ANGLE_HIGH: 180, - }, - True, - ) - assert device.enable_oscillation.call_count == 0 - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_ANGLE, - { - ATTR_ENTITY_ID: "fan.living_room", - dyson.ATTR_ANGLE_LOW: 90, - dyson.ATTR_ANGLE_HIGH: 180, - }, - True, - ) - device.enable_oscillation.assert_called_with(90, 180) - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_set_flow_direction_front(devices, login, hass): - """Test set frontal flow direction.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_FLOW_DIRECTION_FRONT, - {ATTR_ENTITY_ID: "fan.bed_room", dyson.ATTR_FLOW_DIRECTION_FRONT: True}, - True, - ) - assert device.enable_frontal_direction.call_count == 0 - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_FLOW_DIRECTION_FRONT, - {ATTR_ENTITY_ID: "fan.living_room", dyson.ATTR_FLOW_DIRECTION_FRONT: True}, - True, - ) - assert device.enable_frontal_direction.call_count == 1 - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_FLOW_DIRECTION_FRONT, - {ATTR_ENTITY_ID: "fan.living_room", dyson.ATTR_FLOW_DIRECTION_FRONT: False}, - True, - ) - assert device.disable_frontal_direction.call_count == 1 - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_set_timer(devices, login, hass): - """Test set timer.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_TIMER, - {ATTR_ENTITY_ID: "fan.bed_room", dyson.ATTR_TIMER: 60}, - True, - ) - assert device.enable_frontal_direction.call_count == 0 - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_TIMER, - {ATTR_ENTITY_ID: "fan.living_room", dyson.ATTR_TIMER: 60}, - True, - ) - device.enable_sleep_timer.assert_called_with(60) - - await hass.services.async_call( - dyson.DYSON_DOMAIN, - dyson.SERVICE_SET_TIMER, - {ATTR_ENTITY_ID: "fan.living_room", dyson.ATTR_TIMER: 0}, - True, - ) - assert device.disable_sleep_timer.call_count == 1 - - -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_update_state(devices, login, hass): - """Test state update.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - event = { - "msg": "CURRENT-STATE", - "product-state": { - "fpwr": "OFF", - "fdir": "OFF", - "auto": "OFF", - "oscs": "ON", - "oson": "ON", - "nmod": "OFF", - "rhtm": "ON", - "fnst": "FAN", - "ercd": "11E1", - "wacd": "NONE", - "nmdv": "0004", - "fnsp": "0002", - "bril": "0002", - "corf": "ON", - "cflr": "0085", - "hflr": "0095", - "sltm": "OFF", - "osal": "0045", - "osau": "0095", - "ancp": "CUST", - }, - } - device.state = DysonPureCoolV2State(json.dumps(event)) - - for call in device.add_message_listener.call_args_list: - callback = call[0][0] - if type(callback.__self__) == dyson.DysonPureCoolEntity: - callback(device.state) - - await hass.async_block_till_done() - fan_state = hass.states.get("fan.living_room") - attributes = fan_state.attributes - - assert fan_state.state == "off" - assert attributes[dyson.ATTR_NIGHT_MODE] is False - assert attributes[dyson.ATTR_AUTO_MODE] is False - assert attributes[dyson.ATTR_ANGLE_LOW] == 45 - assert attributes[dyson.ATTR_ANGLE_HIGH] == 95 - assert attributes[dyson.ATTR_FLOW_DIRECTION_FRONT] is False - assert attributes[dyson.ATTR_TIMER] == "OFF" - assert attributes[dyson.ATTR_HEPA_FILTER] == 95 - assert attributes[dyson.ATTR_CARBON_FILTER] == 85 - assert attributes[dyson.ATTR_DYSON_SPEED] == int(FanSpeed.FAN_SPEED_2.value) - assert attributes[ATTR_SPEED] is SPEED_LOW + device.state.fan_mode = FanMode.AUTO.value + device.state.speed = FanSpeed.FAN_SPEED_AUTO.value + device.state.night_mode = "OFF" + device.state.oscillation = "OFF" + await async_update_device(hass, device, DysonPureCoolState) + state = hass.states.get(ENTITY_ID) + assert state.state == STATE_ON + attributes = state.attributes + assert attributes[ATTR_NIGHT_MODE] is False assert attributes[ATTR_OSCILLATING] is False - assert attributes[dyson.ATTR_DYSON_SPEED_LIST] == _get_supported_speeds() + assert attributes[ATTR_SPEED] == "AUTO" + assert attributes[ATTR_AUTO_MODE] is True -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], -) -async def test_purecool_update_state_filter_inv(devices, login, hass): - """Test state TP06 carbon filter state.""" - device = devices.return_value[0] - await async_setup_component(hass, dyson.DYSON_DOMAIN, _get_config()) - await hass.async_block_till_done() - event = { - "msg": "CURRENT-STATE", - "product-state": { - "fpwr": "OFF", - "fdir": "ON", - "auto": "ON", - "oscs": "ON", - "oson": "ON", - "nmod": "ON", - "rhtm": "ON", - "fnst": "FAN", - "ercd": "11E1", - "wacd": "NONE", - "nmdv": "0004", - "fnsp": "0002", - "bril": "0002", - "corf": "ON", - "cflr": "INV", - "hflr": "0075", - "sltm": "OFF", - "osal": "0055", - "osau": "0105", - "ancp": "CUST", - }, - } - device.state = DysonPureCoolV2State(json.dumps(event)) +@pytest.mark.parametrize("device", [DysonPureCool], indirect=True) +async def test_state_purecool(hass: HomeAssistant, device: DysonPureCool) -> None: + """Test the state of a PureCool fan.""" + er = await entity_registry.async_get_registry(hass) + assert er.async_get(ENTITY_ID).unique_id == SERIAL - for call in device.add_message_listener.call_args_list: - callback = call[0][0] - if type(callback.__self__) == dyson.DysonPureCoolEntity: - callback(device.state) + state = hass.states.get(ENTITY_ID) + assert state.state == STATE_ON + assert state.name == NAME + attributes = state.attributes + assert attributes[ATTR_NIGHT_MODE] is True + assert attributes[ATTR_OSCILLATING] is True + assert attributes[ATTR_ANGLE_LOW] == 24 + assert attributes[ATTR_ANGLE_HIGH] == 254 + assert attributes[ATTR_SPEED] == SPEED_LOW + assert attributes[ATTR_SPEED_LIST] == [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] + assert attributes[ATTR_DYSON_SPEED] == 1 + assert attributes[ATTR_DYSON_SPEED_LIST] == list(range(1, 11)) + assert attributes[ATTR_AUTO_MODE] is False + assert attributes[ATTR_FLOW_DIRECTION_FRONT] is True + assert attributes[ATTR_TIMER] == "OFF" + assert attributes[ATTR_HEPA_FILTER] == 100 + assert attributes[ATTR_CARBON_FILTER] == 100 + assert attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_OSCILLATE | SUPPORT_SET_SPEED - await hass.async_block_till_done() - fan_state = hass.states.get("fan.living_room") - attributes = fan_state.attributes - - assert fan_state.state == "off" - assert attributes[dyson.ATTR_NIGHT_MODE] is True - assert attributes[dyson.ATTR_AUTO_MODE] is True - assert attributes[dyson.ATTR_ANGLE_LOW] == 55 - assert attributes[dyson.ATTR_ANGLE_HIGH] == 105 - assert attributes[dyson.ATTR_FLOW_DIRECTION_FRONT] is True - assert attributes[dyson.ATTR_TIMER] == "OFF" - assert attributes[dyson.ATTR_HEPA_FILTER] == 75 - assert attributes[dyson.ATTR_CARBON_FILTER] == "INV" - assert attributes[dyson.ATTR_DYSON_SPEED] == int(FanSpeed.FAN_SPEED_2.value) - assert attributes[ATTR_SPEED] is SPEED_LOW + device.state.auto_mode = "ON" + device.state.night_mode = "OFF" + device.state.oscillation = "OIOF" + device.state.speed = "AUTO" + device.state.front_direction = "OFF" + device.state.sleep_timer = "0120" + device.state.carbon_filter_state = "INV" + await async_update_device(hass, device, DysonPureCoolV2State) + state = hass.states.get(ENTITY_ID) + attributes = state.attributes + assert attributes[ATTR_NIGHT_MODE] is False assert attributes[ATTR_OSCILLATING] is False - assert attributes[dyson.ATTR_DYSON_SPEED_LIST] == _get_supported_speeds() + assert attributes[ATTR_SPEED] == SPEED_MEDIUM + assert attributes[ATTR_DYSON_SPEED] == "AUTO" + assert attributes[ATTR_AUTO_MODE] is True + assert attributes[ATTR_FLOW_DIRECTION_FRONT] is False + assert attributes[ATTR_TIMER] == "0120" + assert attributes[ATTR_CARBON_FILTER] == "INV" + + device.state.fan_power = "OFF" + await async_update_device(hass, device, DysonPureCoolV2State) + state = hass.states.get(ENTITY_ID) + assert state.state == STATE_OFF -@patch("libpurecool.dyson.DysonAccount.login", return_value=True) -@patch( - "libpurecool.dyson.DysonAccount.devices", - return_value=[_get_dyson_purecool_device()], +@pytest.mark.parametrize( + "service,service_data,configuration_args", + [ + (SERVICE_TURN_ON, {}, {"fan_mode": FanMode.FAN}), + (SERVICE_TURN_ON, {ATTR_SPEED: "AUTO"}, {"fan_mode": FanMode.AUTO}), + ( + SERVICE_TURN_ON, + {ATTR_SPEED: 5}, + {"fan_mode": FanMode.FAN, "fan_speed": FanSpeed("0005")}, + ), + (SERVICE_TURN_OFF, {}, {"fan_mode": FanMode.OFF}), + ( + SERVICE_OSCILLATE, + {ATTR_OSCILLATING: True}, + {"oscillation": Oscillation.OSCILLATION_ON}, + ), + ( + SERVICE_OSCILLATE, + {ATTR_OSCILLATING: False}, + {"oscillation": Oscillation.OSCILLATION_OFF}, + ), + (SERVICE_SET_SPEED, {ATTR_SPEED: "AUTO"}, {"fan_mode": FanMode.AUTO}), + ( + SERVICE_SET_SPEED, + {ATTR_SPEED: 5}, + {"fan_mode": FanMode.FAN, "fan_speed": FanSpeed("0005")}, + ), + ], ) -async def test_purecool_component_setup_only_once(devices, login, hass): - """Test if entities are created only once.""" - config = _get_config() - await async_setup_component(hass, dyson_parent.DOMAIN, config) - await hass.async_block_till_done() - discovery.load_platform(hass, "fan", dyson_parent.DOMAIN, {}, config) - await hass.async_block_till_done() +@pytest.mark.parametrize("device", [DysonPureCoolLink], indirect=True) +async def test_commands_purecoollink( + hass: HomeAssistant, + device: DysonPureCoolLink, + service: str, + service_data: dict, + configuration_args: dict, +) -> None: + """Test sending commands to a PureCoolLink fan.""" + await hass.services.async_call( + PLATFORM_DOMAIN, + service, + { + ATTR_ENTITY_ID: ENTITY_ID, + **service_data, + }, + blocking=True, + ) + device.set_configuration.assert_called_once_with(**configuration_args) - fans = [ - fan - for fan in hass.data[DOMAIN].entities - if fan.platform.platform_name == dyson_parent.DOMAIN - ] - assert len(fans) == 1 - assert fans[0].device_serial == "XX-XXXXX-XX" +@pytest.mark.parametrize( + "service,service_data,command,command_args", + [ + (SERVICE_TURN_ON, {}, "turn_on", []), + ( + SERVICE_TURN_ON, + {ATTR_SPEED: SPEED_LOW}, + "set_fan_speed", + [FanSpeed.FAN_SPEED_4], + ), + (SERVICE_TURN_OFF, {}, "turn_off", []), + (SERVICE_OSCILLATE, {ATTR_OSCILLATING: True}, "enable_oscillation", []), + (SERVICE_OSCILLATE, {ATTR_OSCILLATING: False}, "disable_oscillation", []), + ( + SERVICE_SET_SPEED, + {ATTR_SPEED: SPEED_LOW}, + "set_fan_speed", + [FanSpeed.FAN_SPEED_4], + ), + ( + SERVICE_SET_SPEED, + {ATTR_SPEED: SPEED_MEDIUM}, + "set_fan_speed", + [FanSpeed.FAN_SPEED_7], + ), + ( + SERVICE_SET_SPEED, + {ATTR_SPEED: SPEED_HIGH}, + "set_fan_speed", + [FanSpeed.FAN_SPEED_10], + ), + ], +) +@pytest.mark.parametrize("device", [DysonPureCool], indirect=True) +async def test_commands_purecool( + hass: HomeAssistant, + device: DysonPureCool, + service: str, + service_data: dict, + command: str, + command_args: list, +) -> None: + """Test sending commands to a PureCool fan.""" + await hass.services.async_call( + PLATFORM_DOMAIN, + service, + { + ATTR_ENTITY_ID: ENTITY_ID, + **service_data, + }, + blocking=True, + ) + getattr(device, command).assert_called_once_with(*command_args) + + +@pytest.mark.parametrize( + "service,service_data,configuration_args", + [ + ( + SERVICE_SET_NIGHT_MODE, + {ATTR_NIGHT_MODE: True}, + {"night_mode": NightMode.NIGHT_MODE_ON}, + ), + ( + SERVICE_SET_NIGHT_MODE, + {ATTR_NIGHT_MODE: False}, + {"night_mode": NightMode.NIGHT_MODE_OFF}, + ), + (SERVICE_SET_AUTO_MODE, {"auto_mode": True}, {"fan_mode": FanMode.AUTO}), + (SERVICE_SET_AUTO_MODE, {"auto_mode": False}, {"fan_mode": FanMode.FAN}), + ], +) +@pytest.mark.parametrize("device", [DysonPureCoolLink], indirect=True) +async def test_custom_services_purecoollink( + hass: HomeAssistant, + device: DysonPureCoolLink, + service: str, + service_data: dict, + configuration_args: dict, +) -> None: + """Test custom services of a PureCoolLink fan.""" + await hass.services.async_call( + DOMAIN, + service, + { + ATTR_ENTITY_ID: ENTITY_ID, + **service_data, + }, + blocking=True, + ) + device.set_configuration.assert_called_once_with(**configuration_args) + + +@pytest.mark.parametrize( + "service,service_data,command,command_args", + [ + (SERVICE_SET_NIGHT_MODE, {ATTR_NIGHT_MODE: True}, "enable_night_mode", []), + (SERVICE_SET_NIGHT_MODE, {ATTR_NIGHT_MODE: False}, "disable_night_mode", []), + (SERVICE_SET_AUTO_MODE, {ATTR_AUTO_MODE: True}, "enable_auto_mode", []), + (SERVICE_SET_AUTO_MODE, {ATTR_AUTO_MODE: False}, "disable_auto_mode", []), + (SERVICE_SET_AUTO_MODE, {ATTR_AUTO_MODE: False}, "disable_auto_mode", []), + ( + SERVICE_SET_ANGLE, + {ATTR_ANGLE_LOW: 10, ATTR_ANGLE_HIGH: 200}, + "enable_oscillation", + [10, 200], + ), + ( + SERVICE_SET_FLOW_DIRECTION_FRONT, + {ATTR_FLOW_DIRECTION_FRONT: True}, + "enable_frontal_direction", + [], + ), + ( + SERVICE_SET_FLOW_DIRECTION_FRONT, + {ATTR_FLOW_DIRECTION_FRONT: False}, + "disable_frontal_direction", + [], + ), + (SERVICE_SET_TIMER, {ATTR_TIMER: 0}, "disable_sleep_timer", []), + (SERVICE_SET_TIMER, {ATTR_TIMER: 10}, "enable_sleep_timer", [10]), + ( + SERVICE_SET_DYSON_SPEED, + {ATTR_DYSON_SPEED: "4"}, + "set_fan_speed", + [FanSpeed("0004")], + ), + ], +) +@pytest.mark.parametrize("device", [DysonPureCool], indirect=True) +async def test_custom_services_purecool( + hass: HomeAssistant, + device: DysonPureCool, + service: str, + service_data: dict, + command: str, + command_args: list, +) -> None: + """Test custom services of a PureCool fan.""" + await hass.services.async_call( + DOMAIN, + service, + { + ATTR_ENTITY_ID: ENTITY_ID, + **service_data, + }, + blocking=True, + ) + getattr(device, command).assert_called_once_with(*command_args) diff --git a/tests/components/dyson/test_vacuum.py b/tests/components/dyson/test_vacuum.py index c43c9d80e66..e12752b6dc4 100644 --- a/tests/components/dyson/test_vacuum.py +++ b/tests/components/dyson/test_vacuum.py @@ -1,6 +1,4 @@ """Test the Dyson 360 eye robot vacuum component.""" -from unittest.mock import MagicMock - from libpurecool.const import Dyson360EyeMode, PowerMode from libpurecool.dyson_360_eye import Dyson360Eye import pytest @@ -34,11 +32,10 @@ ENTITY_ID = f"{PLATFORM_DOMAIN}.{ENTITY_NAME}" @callback -def get_device() -> Dyson360Eye: +def get_device(state=Dyson360EyeMode.FULL_CLEAN_RUNNING) -> Dyson360Eye: """Return a Dyson 360 Eye device.""" device = get_basic_device(Dyson360Eye) - device.state = MagicMock() - device.state.state = Dyson360EyeMode.FULL_CLEAN_RUNNING + device.state.state = state device.state.battery_level = 85 device.state.power_mode = PowerMode.QUIET device.state.position = (0, 0) @@ -77,7 +74,7 @@ async def test_state(hass: HomeAssistant, device: Dyson360Eye) -> None: @pytest.mark.parametrize( - "service,command,state", + "service,command,device", [ (SERVICE_TURN_ON, "start", Dyson360EyeMode.INACTIVE_CHARGED), (SERVICE_TURN_ON, "resume", Dyson360EyeMode.FULL_CLEAN_PAUSED), @@ -89,13 +86,12 @@ async def test_state(hass: HomeAssistant, device: Dyson360Eye) -> None: (SERVICE_START_PAUSE, "resume", Dyson360EyeMode.FULL_CLEAN_PAUSED), (SERVICE_RETURN_TO_BASE, "abort", Dyson360EyeMode.FULL_CLEAN_PAUSED), ], + indirect=["device"], ) async def test_commands( - hass: HomeAssistant, device: Dyson360Eye, service: str, command: str, state: str + hass: HomeAssistant, device: Dyson360Eye, service: str, command: str ) -> None: """Test sending commands to the vacuum.""" - device.state.state = state - await async_update_device(hass, device) await hass.services.async_call( PLATFORM_DOMAIN, service, {ATTR_ENTITY_ID: ENTITY_ID}, blocking=True )