* Added support for dyson hot+cool fan as climate device * Removed decimal place in kelvin units conversion Minor edits to be consistent with Dyson's internal conversion of temperature from kelvin to celsius. It does not include decimal place to convert between kelvin and celsius. * made changes according to comments * Refactored target temp logics, fixed enum issues * changed name of component to entity * removed temperature conversion for min/max property * changed back to 644 permission * added extra tests for almost-all coverage * changed assert method to avoid lack of certain method in py35 * added test_setup_component * shorten line length * fixed mock spec and added checking of message listener is called * added doc string and debug msg * shorten line length * removed pending target temp
359 lines
14 KiB
Python
359 lines
14 KiB
Python
"""Test the Dyson fan component."""
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from libpurecoollink.const import (FocusMode, HeatMode, HeatState, HeatTarget,
|
|
TiltState)
|
|
from libpurecoollink.dyson_pure_state import DysonPureHotCoolState
|
|
from libpurecoollink.dyson_pure_hotcool_link import DysonPureHotCoolLink
|
|
from homeassistant.components.climate import dyson
|
|
from homeassistant.components import dyson as dyson_parent
|
|
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
|
|
from homeassistant.setup import setup_component
|
|
from tests.common import get_test_home_assistant
|
|
|
|
|
|
class MockDysonState(DysonPureHotCoolState):
|
|
"""Mock Dyson state."""
|
|
|
|
def __init__(self):
|
|
"""Create new Mock Dyson State."""
|
|
pass
|
|
|
|
|
|
def _get_device_with_no_state():
|
|
"""Return a device with no state."""
|
|
device = mock.Mock(spec=DysonPureHotCoolLink)
|
|
device.name = "Device_name"
|
|
device.state = None
|
|
device.environmental_state = None
|
|
return device
|
|
|
|
|
|
def _get_device_off():
|
|
"""Return a device with state off."""
|
|
device = mock.Mock(spec=DysonPureHotCoolLink)
|
|
device.name = "Device_name"
|
|
device.state = mock.Mock()
|
|
device.environmental_state = mock.Mock()
|
|
return device
|
|
|
|
|
|
def _get_device_focus():
|
|
"""Return a device with fan state of focus mode."""
|
|
device = mock.Mock(spec=DysonPureHotCoolLink)
|
|
device.name = "Device_name"
|
|
device.state.focus_mode = FocusMode.FOCUS_ON.value
|
|
return device
|
|
|
|
|
|
def _get_device_diffuse():
|
|
"""Return a device with fan state of diffuse mode."""
|
|
device = mock.Mock(spec=DysonPureHotCoolLink)
|
|
device.name = "Device_name"
|
|
device.state.focus_mode = FocusMode.FOCUS_OFF.value
|
|
return device
|
|
|
|
|
|
def _get_device_cool():
|
|
"""Return a device with state of cooling."""
|
|
device = mock.Mock(spec=DysonPureHotCoolLink)
|
|
device.name = "Device_name"
|
|
device.state.tilt = TiltState.TILT_FALSE.value
|
|
device.state.focus_mode = FocusMode.FOCUS_OFF.value
|
|
device.state.heat_target = HeatTarget.celsius(12)
|
|
device.state.heat_mode = HeatMode.HEAT_OFF.value
|
|
device.state.heat_state = HeatState.HEAT_STATE_OFF.value
|
|
device.environmental_state.temperature = 288
|
|
device.environmental_state.humidity = 53
|
|
return device
|
|
|
|
|
|
def _get_device_heat_off():
|
|
"""Return a device with state of heat reached target."""
|
|
device = mock.Mock(spec=DysonPureHotCoolLink)
|
|
device.name = "Device_name"
|
|
device.state = mock.Mock()
|
|
device.state.tilt = TiltState.TILT_FALSE.value
|
|
device.state.focus_mode = FocusMode.FOCUS_ON.value
|
|
device.state.heat_target = HeatTarget.celsius(20)
|
|
device.state.heat_mode = HeatMode.HEAT_ON.value
|
|
device.state.heat_state = HeatState.HEAT_STATE_OFF.value
|
|
device.environmental_state.temperature = 293
|
|
device.environmental_state.humidity = 53
|
|
return device
|
|
|
|
|
|
def _get_device_heat_on():
|
|
"""Return a device with state of heating."""
|
|
device = mock.Mock(spec=DysonPureHotCoolLink)
|
|
device.name = "Device_name"
|
|
device.state = mock.Mock()
|
|
device.state.tilt = TiltState.TILT_FALSE.value
|
|
device.state.focus_mode = FocusMode.FOCUS_ON.value
|
|
device.state.heat_target = HeatTarget.celsius(23)
|
|
device.state.heat_mode = HeatMode.HEAT_ON.value
|
|
device.state.heat_state = HeatState.HEAT_STATE_ON.value
|
|
device.environmental_state.temperature = 289
|
|
device.environmental_state.humidity = 53
|
|
return device
|
|
|
|
|
|
class DysonTest(unittest.TestCase):
|
|
"""Dyson Climate 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()
|
|
|
|
def tearDown(self): # pylint: disable=invalid-name
|
|
"""Stop everything that was started."""
|
|
self.hass.stop()
|
|
|
|
@mock.patch('libpurecoollink.dyson.DysonAccount.devices',
|
|
return_value=[_get_device_heat_on(), _get_device_cool()])
|
|
@mock.patch('libpurecoollink.dyson.DysonAccount.login', return_value=True)
|
|
def test_setup_component_with_parent_discovery(self, mocked_login,
|
|
mocked_devices):
|
|
"""Test setup_component using discovery."""
|
|
setup_component(self.hass, dyson_parent.DOMAIN, {
|
|
dyson_parent.DOMAIN: {
|
|
dyson_parent.CONF_USERNAME: "email",
|
|
dyson_parent.CONF_PASSWORD: "password",
|
|
dyson_parent.CONF_LANGUAGE: "US",
|
|
}
|
|
})
|
|
self.assertEqual(len(self.hass.data[dyson.DYSON_DEVICES]), 2)
|
|
self.hass.block_till_done()
|
|
for m in mocked_devices.return_value:
|
|
assert m.add_message_listener.called
|
|
|
|
def test_setup_component_without_devices(self):
|
|
"""Test setup component with no devices."""
|
|
self.hass.data[dyson.DYSON_DEVICES] = []
|
|
add_devices = mock.MagicMock()
|
|
dyson.setup_platform(self.hass, None, add_devices)
|
|
add_devices.assert_not_called()
|
|
|
|
def test_setup_component_with_devices(self):
|
|
"""Test setup component with valid devices."""
|
|
devices = [
|
|
_get_device_with_no_state(),
|
|
_get_device_off(),
|
|
_get_device_heat_on()
|
|
]
|
|
self.hass.data[dyson.DYSON_DEVICES] = devices
|
|
add_devices = mock.MagicMock()
|
|
dyson.setup_platform(self.hass, None, add_devices, discovery_info={})
|
|
self.assertTrue(add_devices.called)
|
|
|
|
def test_setup_component_with_invalid_devices(self):
|
|
"""Test setup component with invalid devices."""
|
|
devices = [
|
|
None,
|
|
"foo_bar"
|
|
]
|
|
self.hass.data[dyson.DYSON_DEVICES] = devices
|
|
add_devices = mock.MagicMock()
|
|
dyson.setup_platform(self.hass, None, add_devices, discovery_info={})
|
|
add_devices.assert_called_with([])
|
|
|
|
def test_setup_component(self):
|
|
"""Test setup component with devices."""
|
|
device_fan = _get_device_heat_on()
|
|
device_non_fan = _get_device_off()
|
|
|
|
def _add_device(devices):
|
|
assert len(devices) == 1
|
|
assert devices[0].name == "Device_name"
|
|
|
|
self.hass.data[dyson.DYSON_DEVICES] = [device_fan, device_non_fan]
|
|
dyson.setup_platform(self.hass, None, _add_device)
|
|
|
|
def test_dyson_set_temperature(self):
|
|
"""Test set climate temperature."""
|
|
device = _get_device_heat_on()
|
|
device.temp_unit = TEMP_CELSIUS
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertFalse(entity.should_poll)
|
|
|
|
# Without target temp.
|
|
kwargs = {}
|
|
entity.set_temperature(**kwargs)
|
|
set_config = device.set_configuration
|
|
set_config.assert_not_called()
|
|
|
|
kwargs = {ATTR_TEMPERATURE: 23}
|
|
entity.set_temperature(**kwargs)
|
|
set_config = device.set_configuration
|
|
set_config.assert_called_with(
|
|
heat_mode=HeatMode.HEAT_ON,
|
|
heat_target=HeatTarget.celsius(23))
|
|
|
|
# Should clip the target temperature between 1 and 37 inclusive.
|
|
kwargs = {ATTR_TEMPERATURE: 50}
|
|
entity.set_temperature(**kwargs)
|
|
set_config = device.set_configuration
|
|
set_config.assert_called_with(
|
|
heat_mode=HeatMode.HEAT_ON,
|
|
heat_target=HeatTarget.celsius(37))
|
|
|
|
kwargs = {ATTR_TEMPERATURE: -5}
|
|
entity.set_temperature(**kwargs)
|
|
set_config = device.set_configuration
|
|
set_config.assert_called_with(
|
|
heat_mode=HeatMode.HEAT_ON,
|
|
heat_target=HeatTarget.celsius(1))
|
|
|
|
def test_dyson_set_temperature_when_cooling_mode(self):
|
|
"""Test set climate temperature when heating is off."""
|
|
device = _get_device_cool()
|
|
device.temp_unit = TEMP_CELSIUS
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
entity.schedule_update_ha_state = mock.Mock()
|
|
|
|
kwargs = {ATTR_TEMPERATURE: 23}
|
|
entity.set_temperature(**kwargs)
|
|
set_config = device.set_configuration
|
|
set_config.assert_called_with(
|
|
heat_mode=HeatMode.HEAT_ON,
|
|
heat_target=HeatTarget.celsius(23))
|
|
|
|
def test_dyson_set_fan_mode(self):
|
|
"""Test set fan mode."""
|
|
device = _get_device_heat_on()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertFalse(entity.should_poll)
|
|
|
|
entity.set_fan_mode(dyson.STATE_FOCUS)
|
|
set_config = device.set_configuration
|
|
set_config.assert_called_with(focus_mode=FocusMode.FOCUS_ON)
|
|
|
|
entity.set_fan_mode(dyson.STATE_DIFFUSE)
|
|
set_config = device.set_configuration
|
|
set_config.assert_called_with(focus_mode=FocusMode.FOCUS_OFF)
|
|
|
|
def test_dyson_fan_list(self):
|
|
"""Test get fan list."""
|
|
device = _get_device_heat_on()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(len(entity.fan_list), 2)
|
|
self.assertTrue(dyson.STATE_FOCUS in entity.fan_list)
|
|
self.assertTrue(dyson.STATE_DIFFUSE in entity.fan_list)
|
|
|
|
def test_dyson_fan_mode_focus(self):
|
|
"""Test fan focus mode."""
|
|
device = _get_device_focus()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(entity.current_fan_mode, dyson.STATE_FOCUS)
|
|
|
|
def test_dyson_fan_mode_diffuse(self):
|
|
"""Test fan diffuse mode."""
|
|
device = _get_device_diffuse()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(entity.current_fan_mode, dyson.STATE_DIFFUSE)
|
|
|
|
def test_dyson_set_operation_mode(self):
|
|
"""Test set operation mode."""
|
|
device = _get_device_heat_on()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertFalse(entity.should_poll)
|
|
|
|
entity.set_operation_mode(dyson.STATE_HEAT)
|
|
set_config = device.set_configuration
|
|
set_config.assert_called_with(heat_mode=HeatMode.HEAT_ON)
|
|
|
|
entity.set_operation_mode(dyson.STATE_COOL)
|
|
set_config = device.set_configuration
|
|
set_config.assert_called_with(heat_mode=HeatMode.HEAT_OFF)
|
|
|
|
def test_dyson_operation_list(self):
|
|
"""Test get operation list."""
|
|
device = _get_device_heat_on()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(len(entity.operation_list), 2)
|
|
self.assertTrue(dyson.STATE_HEAT in entity.operation_list)
|
|
self.assertTrue(dyson.STATE_COOL in entity.operation_list)
|
|
|
|
def test_dyson_heat_off(self):
|
|
"""Test turn off heat."""
|
|
device = _get_device_heat_off()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
entity.set_operation_mode(dyson.STATE_COOL)
|
|
set_config = device.set_configuration
|
|
set_config.assert_called_with(heat_mode=HeatMode.HEAT_OFF)
|
|
|
|
def test_dyson_heat_on(self):
|
|
"""Test turn on heat."""
|
|
device = _get_device_heat_on()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
entity.set_operation_mode(dyson.STATE_HEAT)
|
|
set_config = device.set_configuration
|
|
set_config.assert_called_with(heat_mode=HeatMode.HEAT_ON)
|
|
|
|
def test_dyson_heat_value_on(self):
|
|
"""Test get heat value on."""
|
|
device = _get_device_heat_on()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(entity.current_operation, dyson.STATE_HEAT)
|
|
|
|
def test_dyson_heat_value_off(self):
|
|
"""Test get heat value off."""
|
|
device = _get_device_cool()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(entity.current_operation, dyson.STATE_COOL)
|
|
|
|
def test_dyson_heat_value_idle(self):
|
|
"""Test get heat value idle."""
|
|
device = _get_device_heat_off()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(entity.current_operation, dyson.STATE_IDLE)
|
|
|
|
def test_on_message(self):
|
|
"""Test when message is received."""
|
|
device = _get_device_heat_on()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
entity.schedule_update_ha_state = mock.Mock()
|
|
entity.on_message(MockDysonState())
|
|
entity.schedule_update_ha_state.assert_called_with()
|
|
|
|
def test_general_properties(self):
|
|
"""Test properties of entity."""
|
|
device = _get_device_with_no_state()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(entity.should_poll, False)
|
|
self.assertEqual(entity.supported_features, dyson.SUPPORT_FLAGS)
|
|
self.assertEqual(entity.temperature_unit, TEMP_CELSIUS)
|
|
|
|
def test_property_current_humidity(self):
|
|
"""Test properties of current humidity."""
|
|
device = _get_device_heat_on()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(entity.current_humidity, 53)
|
|
|
|
def test_property_current_humidity_with_invalid_env_state(self):
|
|
"""Test properties of current humidity with invalid env state."""
|
|
device = _get_device_off()
|
|
device.environmental_state.humidity = 0
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(entity.current_humidity, None)
|
|
|
|
def test_property_current_humidity_without_env_state(self):
|
|
"""Test properties of current humidity without env state."""
|
|
device = _get_device_with_no_state()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(entity.current_humidity, None)
|
|
|
|
def test_property_current_temperature(self):
|
|
"""Test properties of current temperature."""
|
|
device = _get_device_heat_on()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
# Result should be in celsius, hence then subtraction of 273.
|
|
self.assertEqual(entity.current_temperature, 289 - 273)
|
|
|
|
def test_property_target_temperature(self):
|
|
"""Test properties of target temperature."""
|
|
device = _get_device_heat_on()
|
|
entity = dyson.DysonPureHotCoolLinkDevice(device)
|
|
self.assertEqual(entity.target_temperature, 23)
|