From a221b10694a5b28270c28937c9aca6878253e899 Mon Sep 17 00:00:00 2001 From: Eugenio Panadero <eugenio.panadero@gmail.com> Date: Sat, 5 Aug 2017 21:45:59 +0200 Subject: [PATCH] Update xiaomi vacuum tests and include in coverage (#8845) * Fix tests for Demo vacuum platform (and increase coverage) * increase coverage of xiaomi vacuum tests and include in coverage Also little fixes * remove print statement --- .coveragerc | 6 +- homeassistant/components/vacuum/__init__.py | 15 +- homeassistant/components/vacuum/demo.py | 17 +- homeassistant/components/vacuum/xiaomi.py | 30 ++-- tests/components/vacuum/test_demo.py | 63 ++++++- tests/components/vacuum/test_xiaomi.py | 186 ++++++++++++++++---- 6 files changed, 240 insertions(+), 77 deletions(-) diff --git a/.coveragerc b/.coveragerc index 67b4d8be258..1ab8e6c1346 100644 --- a/.coveragerc +++ b/.coveragerc @@ -197,7 +197,11 @@ omit = homeassistant/components/*/wink.py homeassistant/components/xiaomi.py - homeassistant/components/*/xiaomi.py + homeassistant/components/binary_sensor/xiaomi.py + homeassistant/components/cover/xiaomi.py + homeassistant/components/light/xiaomi.py + homeassistant/components/sensor/xiaomi.py + homeassistant/components/switch/xiaomi.py homeassistant/components/zabbix.py homeassistant/components/*/zabbix.py diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py index 078be8e259f..ea12435c05d 100644 --- a/homeassistant/components/vacuum/__init__.py +++ b/homeassistant/components/vacuum/__init__.py @@ -184,9 +184,6 @@ def async_setup(hass, config): def async_handle_vacuum_service(service): """Map services to methods on VacuumDevice.""" method = SERVICE_TO_METHOD.get(service.service) - if not method: - return - target_vacuums = component.async_extract_from_service(service) params = service.data.copy() params.pop(ATTR_ENTITY_ID, None) @@ -223,17 +220,17 @@ class VacuumDevice(ToggleEntity): @property def supported_features(self): """Flag vacuum cleaner features that are supported.""" - return 0 + raise NotImplementedError() @property def status(self): """Return the status of the vacuum cleaner.""" - return None + raise NotImplementedError() @property def battery_level(self): """Return the battery level of the vacuum cleaner.""" - return None + raise NotImplementedError() @property def battery_icon(self): @@ -247,12 +244,12 @@ class VacuumDevice(ToggleEntity): @property def fan_speed(self): """Return the fan speed of the vacuum cleaner.""" - return None + raise NotImplementedError() @property - def fan_speed_list(self) -> list: + def fan_speed_list(self): """Get the list of available fan speed steps of the vacuum cleaner.""" - return [] + raise NotImplementedError() @property def state_attributes(self): diff --git a/homeassistant/components/vacuum/demo.py b/homeassistant/components/vacuum/demo.py index aecf1eb3cf1..a3a9bb24314 100644 --- a/homeassistant/components/vacuum/demo.py +++ b/homeassistant/components/vacuum/demo.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/demo/ import logging from homeassistant.components.vacuum import ( - ATTR_CLEANED_AREA, DEFAULT_ICON, SUPPORT_BATTERY, SUPPORT_FAN_SPEED, + ATTR_CLEANED_AREA, SUPPORT_BATTERY, SUPPORT_FAN_SPEED, SUPPORT_LOCATE, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND, SUPPORT_STATUS, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, VacuumDevice) @@ -32,6 +32,7 @@ DEMO_VACUUM_COMPLETE = '0_Ground_floor' DEMO_VACUUM_MOST = '1_First_floor' DEMO_VACUUM_BASIC = '2_Second_floor' DEMO_VACUUM_MINIMAL = '3_Third_floor' +DEMO_VACUUM_NONE = '4_Fourth_floor' def setup_platform(hass, config, add_devices, discovery_info=None): @@ -41,6 +42,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): DemoVacuum(DEMO_VACUUM_MOST, SUPPORT_MOST_SERVICES), DemoVacuum(DEMO_VACUUM_BASIC, SUPPORT_BASIC_SERVICES), DemoVacuum(DEMO_VACUUM_MINIMAL, SUPPORT_MINIMAL_SERVICES), + DemoVacuum(DEMO_VACUUM_NONE, 0), ]) @@ -48,7 +50,7 @@ class DemoVacuum(VacuumDevice): """Representation of a demo vacuum.""" # pylint: disable=no-self-use - def __init__(self, name, supported_features=None): + def __init__(self, name, supported_features): """Initialize the vacuum.""" self._name = name self._supported_features = supported_features @@ -66,7 +68,7 @@ class DemoVacuum(VacuumDevice): @property def icon(self): """Return the icon for the vacuum.""" - return DEFAULT_ICON + return 'mdi:roomba' @property def should_poll(self): @@ -97,9 +99,7 @@ class DemoVacuum(VacuumDevice): @property def fan_speed_list(self): """Return the status of the vacuum.""" - if self.supported_features & SUPPORT_FAN_SPEED == 0: - return - + assert self.supported_features & SUPPORT_FAN_SPEED != 0 return FAN_SPEEDS @property @@ -118,10 +118,7 @@ class DemoVacuum(VacuumDevice): @property def supported_features(self): """Flag supported features.""" - if self._supported_features is not None: - return self._supported_features - - return super().supported_features + return self._supported_features def turn_on(self, **kwargs): """Turn the vacuum on.""" diff --git a/homeassistant/components/vacuum/xiaomi.py b/homeassistant/components/vacuum/xiaomi.py index 49508d0dce5..2e99e94a7d6 100644 --- a/homeassistant/components/vacuum/xiaomi.py +++ b/homeassistant/components/vacuum/xiaomi.py @@ -103,9 +103,6 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_service_handler(service): """Map services to methods on MiroboVacuum.""" method = SERVICE_TO_METHOD.get(service.service) - if not method: - return - params = {key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID} entity_ids = service.data.get(ATTR_ENTITY_ID) @@ -191,19 +188,19 @@ class MiroboVacuum(VacuumDevice): @property def device_state_attributes(self): """Return the specific state attributes of this vacuum cleaner.""" + attrs = {} if self.vacuum_state is not None: - attrs = { + attrs.update({ ATTR_DO_NOT_DISTURB: STATE_ON if self.vacuum_state.dnd else STATE_OFF, # Not working --> 'Cleaning mode': # STATE_ON if self.vacuum_state.in_cleaning else STATE_OFF, ATTR_CLEANING_TIME: str(self.vacuum_state.clean_time), - ATTR_CLEANED_AREA: round(self.vacuum_state.clean_area, 2)} + ATTR_CLEANED_AREA: round(self.vacuum_state.clean_area, 2)}) if self.vacuum_state.got_error: attrs[ATTR_ERROR] = self.vacuum_state.error - return attrs - return {} + return attrs @property def is_on(self) -> bool: @@ -242,15 +239,15 @@ class MiroboVacuum(VacuumDevice): def async_turn_off(self, **kwargs): """Turn the vacuum off and return to home.""" yield from self.async_stop() - return_home = yield from self.async_return_to_base() - if return_home: - self._is_on = False + yield from self.async_return_to_base() @asyncio.coroutine def async_stop(self, **kwargs): """Stop the vacuum cleaner.""" - yield from self._try_command( + stopped = yield from self._try_command( "Unable to stop: %s", self._vacuum.stop) + if stopped: + self._is_on = False @asyncio.coroutine def async_set_fan_speed(self, fan_speed, **kwargs): @@ -338,17 +335,16 @@ class MiroboVacuum(VacuumDevice): @asyncio.coroutine def async_update(self): """Fetch state from the device.""" - from mirobo import DeviceException + from mirobo import VacuumException try: state = yield from self.hass.async_add_job(self._vacuum.status) - _LOGGER.debug("Got new state from the vacuum: %s", state.data) self.vacuum_state = state self._is_on = state.is_on self._available = True - except DeviceException as ex: - _LOGGER.warning("Got exception while fetching the state: %s", ex) + except OSError as exc: + _LOGGER.error("Got OSError while fetching the state: %s", exc) # self._available = False - except OSError as ex: - _LOGGER.error("Got exception while fetching the state: %s", ex) + except VacuumException as exc: + _LOGGER.warning("Got exception while fetching the state: %s", exc) # self._available = False diff --git a/tests/components/vacuum/test_demo.py b/tests/components/vacuum/test_demo.py index 445aa0a4e88..2b8fb34b92a 100644 --- a/tests/components/vacuum/test_demo.py +++ b/tests/components/vacuum/test_demo.py @@ -9,7 +9,7 @@ from homeassistant.components.vacuum import ( SERVICE_SEND_COMMAND, SERVICE_SET_FAN_SPEED) from homeassistant.components.vacuum.demo import ( DEMO_VACUUM_BASIC, DEMO_VACUUM_COMPLETE, DEMO_VACUUM_MINIMAL, - DEMO_VACUUM_MOST, FAN_SPEEDS) + DEMO_VACUUM_MOST, DEMO_VACUUM_NONE, FAN_SPEEDS) from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, CONF_PLATFORM, STATE_OFF, STATE_ON) from homeassistant.setup import setup_component @@ -20,6 +20,7 @@ ENTITY_VACUUM_BASIC = '{}.{}'.format(DOMAIN, DEMO_VACUUM_BASIC).lower() ENTITY_VACUUM_COMPLETE = '{}.{}'.format(DOMAIN, DEMO_VACUUM_COMPLETE).lower() ENTITY_VACUUM_MINIMAL = '{}.{}'.format(DOMAIN, DEMO_VACUUM_MINIMAL).lower() ENTITY_VACUUM_MOST = '{}.{}'.format(DOMAIN, DEMO_VACUUM_MOST).lower() +ENTITY_VACUUM_NONE = '{}.{}'.format(DOMAIN, DEMO_VACUUM_NONE).lower() class TestVacuumDemo(unittest.TestCase): @@ -70,18 +71,30 @@ class TestVacuumDemo(unittest.TestCase): self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED_LIST)) self.assertEqual(STATE_OFF, state.state) + state = self.hass.states.get(ENTITY_VACUUM_NONE) + self.assertEqual(0, state.attributes.get(ATTR_SUPPORTED_FEATURES)) + self.assertEqual(None, state.attributes.get(ATTR_STATUS)) + self.assertEqual(None, state.attributes.get(ATTR_BATTERY_LEVEL)) + self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED)) + self.assertEqual(None, state.attributes.get(ATTR_FAN_SPEED_LIST)) + self.assertEqual(STATE_OFF, state.state) + def test_methods(self): """Test if methods call the services as expected.""" self.hass.states.set(ENTITY_VACUUM_BASIC, STATE_ON) + self.hass.block_till_done() self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_BASIC)) self.hass.states.set(ENTITY_VACUUM_BASIC, STATE_OFF) + self.hass.block_till_done() self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_BASIC)) self.hass.states.set(ENTITY_ID_ALL_VACUUMS, STATE_ON) + self.hass.block_till_done() self.assertTrue(vacuum.is_on(self.hass)) self.hass.states.set(ENTITY_ID_ALL_VACUUMS, STATE_OFF) + self.hass.block_till_done() self.assertFalse(vacuum.is_on(self.hass)) vacuum.turn_on(self.hass, ENTITY_VACUUM_COMPLETE) @@ -128,6 +141,54 @@ class TestVacuumDemo(unittest.TestCase): state = self.hass.states.get(ENTITY_VACUUM_COMPLETE) self.assertEqual(FAN_SPEEDS[-1], state.attributes.get(ATTR_FAN_SPEED)) + def test_unsupported_methods(self): + """Test service calls for unsupported vacuums.""" + self.hass.states.set(ENTITY_VACUUM_NONE, STATE_ON) + self.hass.block_till_done() + self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + + vacuum.turn_off(self.hass, ENTITY_VACUUM_NONE) + self.hass.block_till_done() + self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + + vacuum.stop(self.hass, ENTITY_VACUUM_NONE) + self.hass.block_till_done() + self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + + self.hass.states.set(ENTITY_VACUUM_NONE, STATE_OFF) + self.hass.block_till_done() + self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + + vacuum.turn_on(self.hass, ENTITY_VACUUM_NONE) + self.hass.block_till_done() + self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + + vacuum.toggle(self.hass, ENTITY_VACUUM_NONE) + self.hass.block_till_done() + self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + + # Non supported methods: + vacuum.start_pause(self.hass, ENTITY_VACUUM_NONE) + self.hass.block_till_done() + self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE)) + + vacuum.locate(self.hass, ENTITY_VACUUM_NONE) + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_VACUUM_NONE) + self.assertIsNone(state.attributes.get(ATTR_STATUS)) + + vacuum.return_to_base(self.hass, ENTITY_VACUUM_NONE) + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_VACUUM_NONE) + self.assertIsNone(state.attributes.get(ATTR_STATUS)) + + vacuum.set_fan_speed(self.hass, FAN_SPEEDS[-1], + entity_id=ENTITY_VACUUM_NONE) + self.hass.block_till_done() + state = self.hass.states.get(ENTITY_VACUUM_NONE) + self.assertNotEqual(FAN_SPEEDS[-1], + state.attributes.get(ATTR_FAN_SPEED)) + def test_services(self): """Test vacuum services.""" # Test send_command diff --git a/tests/components/vacuum/test_xiaomi.py b/tests/components/vacuum/test_xiaomi.py index 5897b76a0be..d5c8cda65c3 100644 --- a/tests/components/vacuum/test_xiaomi.py +++ b/tests/components/vacuum/test_xiaomi.py @@ -9,7 +9,7 @@ from homeassistant.components.vacuum import ( ATTR_BATTERY_ICON, ATTR_FAN_SPEED, ATTR_FAN_SPEED_LIST, DOMAIN, SERVICE_LOCATE, SERVICE_RETURN_TO_BASE, SERVICE_SEND_COMMAND, - SERVICE_SET_FAN_SPEED, SERVICE_STOP, + SERVICE_SET_FAN_SPEED, SERVICE_START_PAUSE, SERVICE_STOP, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON) from homeassistant.components.vacuum.xiaomi import ( ATTR_CLEANED_AREA, ATTR_CLEANING_TIME, ATTR_DO_NOT_DISTURB, ATTR_ERROR, @@ -17,18 +17,20 @@ from homeassistant.components.vacuum.xiaomi import ( SERVICE_MOVE_REMOTE_CONTROL, SERVICE_MOVE_REMOTE_CONTROL_STEP, SERVICE_START_REMOTE_CONTROL, SERVICE_STOP_REMOTE_CONTROL) from homeassistant.const import ( - ATTR_SUPPORTED_FEATURES, CONF_PLATFORM, STATE_OFF, STATE_ON) + ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_PLATFORM, STATE_OFF, + STATE_ON) from homeassistant.setup import async_setup_component @pytest.fixture -def mock_mirobo(): +def mock_mirobo_is_off(): """Mock mock_mirobo.""" mock_vacuum = mock.MagicMock() mock_vacuum.Vacuum().status().data = {'test': 'raw'} mock_vacuum.Vacuum().status().is_on = False mock_vacuum.Vacuum().status().fanspeed = 38 - mock_vacuum.Vacuum().status().got_error = False + mock_vacuum.Vacuum().status().got_error = True + mock_vacuum.Vacuum().status().error = 'Error message' mock_vacuum.Vacuum().status().dnd = True mock_vacuum.Vacuum().status().battery = 82 mock_vacuum.Vacuum().status().clean_area = 123.43218 @@ -42,10 +44,59 @@ def mock_mirobo(): yield mock_vacuum +@pytest.fixture +def mock_mirobo_is_on(): + """Mock mock_mirobo.""" + mock_vacuum = mock.MagicMock() + mock_vacuum.Vacuum().status().data = {'test': 'raw'} + mock_vacuum.Vacuum().status().is_on = True + mock_vacuum.Vacuum().status().fanspeed = 99 + mock_vacuum.Vacuum().status().got_error = False + mock_vacuum.Vacuum().status().dnd = False + mock_vacuum.Vacuum().status().battery = 32 + mock_vacuum.Vacuum().status().clean_area = 133.43218 + mock_vacuum.Vacuum().status().clean_time = timedelta( + hours=2, minutes=55, seconds=34) + mock_vacuum.Vacuum().status().state = 'Test Xiaomi Cleaning' + + with mock.patch.dict('sys.modules', { + 'mirobo': mock_vacuum, + }): + yield mock_vacuum + + +@pytest.fixture +def mock_mirobo_errors(): + """Mock mock_mirobo_errors to simulate a bad vacuum status request.""" + mock_vacuum = mock.MagicMock() + mock_vacuum.Vacuum().status.side_effect = OSError() + with mock.patch.dict('sys.modules', { + 'mirobo': mock_vacuum, + }): + yield mock_vacuum + + @asyncio.coroutine -def test_xiaomi_vacuum(hass, caplog, mock_mirobo): +def test_xiaomi_exceptions(hass, caplog, mock_mirobo_errors): """Test vacuum supported features.""" - entity_name = 'test_vacuum_cleaner' + entity_name = 'test_vacuum_cleaner_error' + yield from async_setup_component( + hass, DOMAIN, + {DOMAIN: {CONF_PLATFORM: PLATFORM, + CONF_HOST: '127.0.0.1', + CONF_NAME: entity_name, + CONF_TOKEN: '12345678901234567890123456789012'}}) + + assert 'Initializing with host 127.0.0.1 (token 12345...)' in caplog.text + assert str(mock_mirobo_errors.mock_calls[-1]) == 'call.Vacuum().status()' + assert 'ERROR' in caplog.text + assert 'Got OSError while fetching the state' in caplog.text + + +@asyncio.coroutine +def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_off): + """Test vacuum supported features.""" + entity_name = 'test_vacuum_cleaner_1' entity_id = '{}.{}'.format(DOMAIN, entity_name) yield from async_setup_component( @@ -63,7 +114,7 @@ def test_xiaomi_vacuum(hass, caplog, mock_mirobo): assert state.state == STATE_OFF assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 1023 assert state.attributes.get(ATTR_DO_NOT_DISTURB) == STATE_ON - assert state.attributes.get(ATTR_ERROR) is None + assert state.attributes.get(ATTR_ERROR) == 'Error message' assert (state.attributes.get(ATTR_BATTERY_ICON) == 'mdi:battery-charging-80') assert state.attributes.get(ATTR_CLEANING_TIME) == '2:35:34' @@ -75,80 +126,137 @@ def test_xiaomi_vacuum(hass, caplog, mock_mirobo): # Call services yield from hass.services.async_call( DOMAIN, SERVICE_TURN_ON, blocking=True) - assert str(mock_mirobo.mock_calls[-2]) == 'call.Vacuum().start()' - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_off.mock_calls[-2]) == 'call.Vacuum().start()' + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' yield from hass.services.async_call( DOMAIN, SERVICE_TURN_OFF, blocking=True) - assert str(mock_mirobo.mock_calls[-2]) == 'call.Vacuum().home()' - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_off.mock_calls[-2]) == 'call.Vacuum().home()' + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' yield from hass.services.async_call( DOMAIN, SERVICE_TOGGLE, blocking=True) - assert str(mock_mirobo.mock_calls[-2]) == 'call.Vacuum().start()' - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_off.mock_calls[-2]) == 'call.Vacuum().start()' + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' yield from hass.services.async_call( DOMAIN, SERVICE_STOP, blocking=True) - assert str(mock_mirobo.mock_calls[-2]) == 'call.Vacuum().stop()' - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_off.mock_calls[-2]) == 'call.Vacuum().stop()' + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' + + yield from hass.services.async_call( + DOMAIN, SERVICE_START_PAUSE, blocking=True) + assert str(mock_mirobo_is_off.mock_calls[-2]) == 'call.Vacuum().start()' + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' yield from hass.services.async_call( DOMAIN, SERVICE_RETURN_TO_BASE, blocking=True) - assert str(mock_mirobo.mock_calls[-2]) == 'call.Vacuum().home()' - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_off.mock_calls[-2]) == 'call.Vacuum().home()' + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' yield from hass.services.async_call( DOMAIN, SERVICE_LOCATE, blocking=True) - assert str(mock_mirobo.mock_calls[-2]) == 'call.Vacuum().find()' - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_off.mock_calls[-2]) == 'call.Vacuum().find()' + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' + # Set speed service: yield from hass.services.async_call( DOMAIN, SERVICE_SET_FAN_SPEED, {"fan_speed": 60}, blocking=True) - assert (str(mock_mirobo.mock_calls[-2]) + assert (str(mock_mirobo_is_off.mock_calls[-2]) == 'call.Vacuum().set_fan_speed(60)') - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' + + yield from hass.services.async_call( + DOMAIN, SERVICE_SET_FAN_SPEED, {"fan_speed": "turbo"}, blocking=True) + assert (str(mock_mirobo_is_off.mock_calls[-2]) + == 'call.Vacuum().set_fan_speed(77)') + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' + + assert 'ERROR' not in caplog.text + yield from hass.services.async_call( + DOMAIN, SERVICE_SET_FAN_SPEED, {"fan_speed": "invent"}, blocking=True) + assert 'ERROR' in caplog.text yield from hass.services.async_call( DOMAIN, SERVICE_SEND_COMMAND, {"command": "raw"}, blocking=True) - assert (str(mock_mirobo.mock_calls[-2]) + assert (str(mock_mirobo_is_off.mock_calls[-2]) == "call.Vacuum().raw_command('raw', None)") - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' yield from hass.services.async_call( DOMAIN, SERVICE_SEND_COMMAND, {"command": "raw", "params": {"k1": 2}}, blocking=True) - assert (str(mock_mirobo.mock_calls[-2]) + assert (str(mock_mirobo_is_off.mock_calls[-2]) == "call.Vacuum().raw_command('raw', {'k1': 2})") - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_off.mock_calls[-1]) == 'call.Vacuum().status()' + + +@asyncio.coroutine +def test_xiaomi_vacuum_specific_services(hass, caplog, mock_mirobo_is_on): + """Test vacuum supported features.""" + entity_name = 'test_vacuum_cleaner_2' + entity_id = '{}.{}'.format(DOMAIN, entity_name) + + yield from async_setup_component( + hass, DOMAIN, + {DOMAIN: {CONF_PLATFORM: PLATFORM, + CONF_HOST: '192.168.1.100', + CONF_NAME: entity_name, + CONF_TOKEN: '12345678901234567890123456789012'}}) + + assert 'Initializing with host 192.168.1.100 (token 12345' in caplog.text + + # Check state attributes + state = hass.states.get(entity_id) + assert state.state == STATE_ON + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 1023 + assert state.attributes.get(ATTR_DO_NOT_DISTURB) == STATE_OFF + assert state.attributes.get(ATTR_ERROR) is None + assert (state.attributes.get(ATTR_BATTERY_ICON) + == 'mdi:battery-30') + assert state.attributes.get(ATTR_CLEANING_TIME) == '2:55:34' + assert state.attributes.get(ATTR_CLEANED_AREA) == 133.43 + assert state.attributes.get(ATTR_FAN_SPEED) == 99 + assert (state.attributes.get(ATTR_FAN_SPEED_LIST) + == ['Quiet', 'Balanced', 'Turbo', 'Max']) + + # Check setting pause + yield from hass.services.async_call( + DOMAIN, SERVICE_START_PAUSE, blocking=True) + assert str(mock_mirobo_is_on.mock_calls[-2]) == 'call.Vacuum().pause()' + assert str(mock_mirobo_is_on.mock_calls[-1]) == 'call.Vacuum().status()' + # Xiaomi vacuum specific services: yield from hass.services.async_call( - DOMAIN, SERVICE_START_REMOTE_CONTROL, {}, blocking=True) - assert (str(mock_mirobo.mock_calls[-2]) + DOMAIN, SERVICE_START_REMOTE_CONTROL, + {ATTR_ENTITY_ID: entity_id}, blocking=True) + assert (str(mock_mirobo_is_on.mock_calls[-2]) == "call.Vacuum().manual_start()") - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_on.mock_calls[-1]) == 'call.Vacuum().status()' yield from hass.services.async_call( DOMAIN, SERVICE_MOVE_REMOTE_CONTROL, {"duration": 1000, "rotation": -40, "velocity": -0.1}, blocking=True) - assert 'call.Vacuum().manual_control(' in str(mock_mirobo.mock_calls[-2]) - assert 'duration=1000' in str(mock_mirobo.mock_calls[-2]) - assert 'rotation=-40' in str(mock_mirobo.mock_calls[-2]) - assert 'velocity=-0.1' in str(mock_mirobo.mock_calls[-2]) - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert ('call.Vacuum().manual_control(' + in str(mock_mirobo_is_on.mock_calls[-2])) + assert 'duration=1000' in str(mock_mirobo_is_on.mock_calls[-2]) + assert 'rotation=-40' in str(mock_mirobo_is_on.mock_calls[-2]) + assert 'velocity=-0.1' in str(mock_mirobo_is_on.mock_calls[-2]) + assert str(mock_mirobo_is_on.mock_calls[-1]) == 'call.Vacuum().status()' yield from hass.services.async_call( DOMAIN, SERVICE_STOP_REMOTE_CONTROL, {}, blocking=True) - assert (str(mock_mirobo.mock_calls[-2]) + assert (str(mock_mirobo_is_on.mock_calls[-2]) == "call.Vacuum().manual_stop()") - assert str(mock_mirobo.mock_calls[-1]) == 'call.Vacuum().status()' + assert str(mock_mirobo_is_on.mock_calls[-1]) == 'call.Vacuum().status()' yield from hass.services.async_call( DOMAIN, SERVICE_MOVE_REMOTE_CONTROL_STEP, {"duration": 2000, "rotation": 120, "velocity": 0.1}, blocking=True) assert ('call.Vacuum().manual_control_once(' - in str(mock_mirobo.mock_calls[-2])) - assert 'duration=2000' in str(mock_mirobo.mock_calls[-2]) - assert 'rotation=120' in str(mock_mirobo.mock_calls[-2]) - assert 'velocity=0.1' in str(mock_mirobo.mock_calls[-2]) + in str(mock_mirobo_is_on.mock_calls[-2])) + assert 'duration=2000' in str(mock_mirobo_is_on.mock_calls[-2]) + assert 'rotation=120' in str(mock_mirobo_is_on.mock_calls[-2]) + assert 'velocity=0.1' in str(mock_mirobo_is_on.mock_calls[-2]) + assert str(mock_mirobo_is_on.mock_calls[-1]) == 'call.Vacuum().status()' -- GitLab