From e7425e9808dc957094aebb25fadf57c0d1a8d977 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Tue, 21 Mar 2017 11:55:21 -0400 Subject: [PATCH] ZWave Lock Tests (#6730) * ZWave Lock Tests * Linting fixes * Missed coveragerc --- .coveragerc | 1 - homeassistant/components/lock/zwave.py | 51 +++-- tests/components/lock/test_zwave.py | 271 +++++++++++++++++++++++++ tests/mock/zwave.py | 2 +- 4 files changed, 302 insertions(+), 23 deletions(-) create mode 100644 tests/components/lock/test_zwave.py diff --git a/.coveragerc b/.coveragerc index c605f15ba57..810764a7708 100644 --- a/.coveragerc +++ b/.coveragerc @@ -231,7 +231,6 @@ omit = homeassistant/components/light/zengge.py homeassistant/components/lirc.py homeassistant/components/lock/nuki.py - homeassistant/components/lock/zwave.py homeassistant/components/media_player/anthemav.py homeassistant/components/media_player/apple_tv.py homeassistant/components/media_player/aquostv.py diff --git a/homeassistant/components/lock/zwave.py b/homeassistant/components/lock/zwave.py index 55e5b6276a6..1a3b9bd662c 100644 --- a/homeassistant/components/lock/zwave.py +++ b/homeassistant/components/lock/zwave.py @@ -6,6 +6,7 @@ https://home-assistant.io/components/lock.zwave/ """ # Because we do not compile openzwave on CI # pylint: disable=import-error +import asyncio import logging from os import path @@ -13,7 +14,6 @@ import voluptuous as vol from homeassistant.components.lock import DOMAIN, LockDevice from homeassistant.components import zwave -from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import from homeassistant.config import load_yaml_config_file import homeassistant.helpers.config_validation as cv @@ -53,7 +53,7 @@ LOCK_ALARM_TYPE = { '9': 'Deadbolt Jammed', '18': 'Locked with Keypad by user ', '19': 'Unlocked with Keypad by user ', - '21': 'Manually Locked by', + '21': 'Manually Locked by ', '22': 'Manually Unlocked by Key or Inside thumb turn', '24': 'Locked by RF', '25': 'Unlocked by RF', @@ -120,8 +120,12 @@ CLEAR_USERCODE_SCHEMA = vol.Schema({ }) -def get_device(hass, node, values, **kwargs): - """Create zwave entity device.""" +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Generic Z-Wave platform setup.""" + yield from zwave.async_setup_platform( + hass, config, async_add_devices, discovery_info) + descriptions = load_yaml_config_file( path.join(path.dirname(__file__), 'services.yaml')) @@ -140,6 +144,7 @@ def get_device(hass, node, values, **kwargs): _LOGGER.error('Invalid code provided: (%s)' ' usercode must %s or less digits', usercode, len(value.data)) + break value.data = str(usercode) break @@ -175,22 +180,25 @@ def get_device(hass, node, values, **kwargs): _LOGGER.info('Usercode at slot %s is cleared', value.index) break - if node.has_command_class(zwave.const.COMMAND_CLASS_USER_CODE): - hass.services.register(DOMAIN, - SERVICE_SET_USERCODE, - set_usercode, - descriptions.get(SERVICE_SET_USERCODE), - schema=SET_USERCODE_SCHEMA) - hass.services.register(DOMAIN, - SERVICE_GET_USERCODE, - get_usercode, - descriptions.get(SERVICE_GET_USERCODE), - schema=GET_USERCODE_SCHEMA) - hass.services.register(DOMAIN, - SERVICE_CLEAR_USERCODE, - clear_usercode, - descriptions.get(SERVICE_CLEAR_USERCODE), - schema=CLEAR_USERCODE_SCHEMA) + hass.services.async_register(DOMAIN, + SERVICE_SET_USERCODE, + set_usercode, + descriptions.get(SERVICE_SET_USERCODE), + schema=SET_USERCODE_SCHEMA) + hass.services.async_register(DOMAIN, + SERVICE_GET_USERCODE, + get_usercode, + descriptions.get(SERVICE_GET_USERCODE), + schema=GET_USERCODE_SCHEMA) + hass.services.async_register(DOMAIN, + SERVICE_CLEAR_USERCODE, + clear_usercode, + descriptions.get(SERVICE_CLEAR_USERCODE), + schema=CLEAR_USERCODE_SCHEMA) + + +def get_device(node, values, **kwargs): + """Create zwave entity device.""" return ZwaveLock(values) @@ -253,7 +261,8 @@ class ZwaveLock(zwave.ZWaveDeviceEntity, LockDevice): self._lock_status = '{}{}'.format( LOCK_ALARM_TYPE.get(str(alarm_type)), MANUAL_LOCK_ALARM_LEVEL.get(str(alarm_level))) - if alarm_type in ALARM_TYPE_STD: + return + if str(alarm_type) in ALARM_TYPE_STD: self._lock_status = '{}{}'.format( LOCK_ALARM_TYPE.get(str(alarm_type)), str(alarm_level)) return diff --git a/tests/components/lock/test_zwave.py b/tests/components/lock/test_zwave.py new file mode 100644 index 00000000000..ff7dd15c4c4 --- /dev/null +++ b/tests/components/lock/test_zwave.py @@ -0,0 +1,271 @@ +"""Test Z-Wave locks.""" +import asyncio + +from unittest.mock import patch, MagicMock + +from homeassistant.components.lock import zwave +from homeassistant.components.zwave import const + +from tests.mock.zwave import ( + MockNode, MockValue, MockEntityValues, value_changed) + + +def test_get_device_detects_lock(mock_openzwave): + """Test get_device returns a Z-Wave lock.""" + node = MockNode() + values = MockEntityValues( + primary=MockValue(data=None, node=node), + access_control=None, + alarm_type=None, + alarm_level=None, + ) + + device = zwave.get_device(node=node, values=values, node_config={}) + assert isinstance(device, zwave.ZwaveLock) + + +def test_lock_turn_on_and_off(mock_openzwave): + """Test turning on a Z-Wave lock.""" + node = MockNode() + values = MockEntityValues( + primary=MockValue(data=None, node=node), + access_control=None, + alarm_type=None, + alarm_level=None, + ) + device = zwave.get_device(node=node, values=values, node_config={}) + + assert not values.primary.data + + device.lock() + assert values.primary.data + + device.unlock() + assert not values.primary.data + + +def test_lock_value_changed(mock_openzwave): + """Test value changed for Z-Wave lock.""" + node = MockNode() + values = MockEntityValues( + primary=MockValue(data=None, node=node), + access_control=None, + alarm_type=None, + alarm_level=None, + ) + device = zwave.get_device(node=node, values=values, node_config={}) + + assert not device.is_locked + + values.primary.data = True + value_changed(values.primary) + + assert device.is_locked + + +def test_v2btze_value_changed(mock_openzwave): + """Test value changed for v2btze Z-Wave lock.""" + node = MockNode(manufacturer_id='010e', product_id='0002') + values = MockEntityValues( + primary=MockValue(data=None, node=node), + v2btze_advanced=MockValue(data='Advanced', node=node), + access_control=MockValue(data=19, node=node), + alarm_type=None, + alarm_level=None, + ) + device = zwave.get_device(node=node, values=values, node_config={}) + assert device._v2btze + + assert not device.is_locked + + values.access_control.data = 24 + value_changed(values.primary) + + assert device.is_locked + + +def test_lock_access_control(mock_openzwave): + """Test access control for Z-Wave lock.""" + node = MockNode() + values = MockEntityValues( + primary=MockValue(data=None, node=node), + access_control=MockValue(data=11, node=node), + alarm_type=None, + alarm_level=None, + ) + device = zwave.get_device(node=node, values=values, node_config={}) + + assert device.device_state_attributes[zwave.ATTR_NOTIFICATION] == \ + 'Lock Jammed' + + +def test_lock_alarm_type(mock_openzwave): + """Test alarm type for Z-Wave lock.""" + node = MockNode() + values = MockEntityValues( + primary=MockValue(data=None, node=node), + access_control=None, + alarm_type=MockValue(data=None, node=node), + alarm_level=None, + ) + device = zwave.get_device(node=node, values=values, node_config={}) + + assert zwave.ATTR_LOCK_STATUS not in device.device_state_attributes + + values.alarm_type.data = 21 + value_changed(values.alarm_type) + assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + 'Manually Locked by None' + + values.alarm_type.data = 18 + value_changed(values.alarm_type) + assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + 'Locked with Keypad by user None' + + values.alarm_type.data = 161 + value_changed(values.alarm_type) + assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + 'Tamper Alarm: None' + + values.alarm_type.data = 9 + value_changed(values.alarm_type) + assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + 'Deadbolt Jammed' + + +def test_lock_alarm_level(mock_openzwave): + """Test alarm level for Z-Wave lock.""" + node = MockNode() + values = MockEntityValues( + primary=MockValue(data=None, node=node), + access_control=None, + alarm_type=MockValue(data=None, node=node), + alarm_level=MockValue(data=None, node=node), + ) + device = zwave.get_device(node=node, values=values, node_config={}) + + assert zwave.ATTR_LOCK_STATUS not in device.device_state_attributes + + values.alarm_type.data = 21 + values.alarm_level.data = 1 + value_changed(values.alarm_type) + value_changed(values.alarm_level) + assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + 'Manually Locked by Key Cylinder or Inside thumb turn' + + values.alarm_type.data = 18 + values.alarm_level.data = 'alice' + value_changed(values.alarm_type) + value_changed(values.alarm_level) + assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + 'Locked with Keypad by user alice' + + values.alarm_type.data = 161 + values.alarm_level.data = 1 + value_changed(values.alarm_type) + value_changed(values.alarm_level) + assert device.device_state_attributes[zwave.ATTR_LOCK_STATUS] == \ + 'Tamper Alarm: Too many keypresses' + + +@asyncio.coroutine +def test_lock_set_usercode_service(hass, mock_openzwave): + """Test the zwave lock set_usercode service.""" + node = MockNode(node_id=12) + value0 = MockValue(data=None, node=node, index=0) + value1 = MockValue(data=None, node=node, index=1) + yield from zwave.async_setup_platform( + hass, {}, MagicMock()) + + node.get_values.return_value = { + value0.value_id: value0, + value1.value_id: value1, + } + + with patch.object(zwave.zwave, 'NETWORK') as mock_network: + mock_network.nodes = { + node.node_id: node + } + yield from hass.services.async_call( + zwave.DOMAIN, zwave.SERVICE_SET_USERCODE, { + const.ATTR_NODE_ID: node.node_id, + zwave.ATTR_USERCODE: '1234', + zwave.ATTR_CODE_SLOT: 1, + }) + yield from hass.async_block_till_done() + + assert value1.data == '1234' + + with patch.object(zwave.zwave, 'NETWORK') as mock_network: + mock_network.nodes = { + node.node_id: node + } + yield from hass.services.async_call( + zwave.DOMAIN, zwave.SERVICE_SET_USERCODE, { + const.ATTR_NODE_ID: node.node_id, + zwave.ATTR_USERCODE: '12345', + zwave.ATTR_CODE_SLOT: 1, + }) + yield from hass.async_block_till_done() + + assert value1.data == '1234' + + +@asyncio.coroutine +def test_lock_get_usercode_service(hass, mock_openzwave): + """Test the zwave lock get_usercode service.""" + node = MockNode(node_id=12) + value0 = MockValue(data=None, node=node, index=0) + value1 = MockValue(data='1234', node=node, index=1) + yield from zwave.async_setup_platform( + hass, {}, MagicMock()) + + node.get_values.return_value = { + value0.value_id: value0, + value1.value_id: value1, + } + + with patch.object(zwave.zwave, 'NETWORK') as mock_network: + with patch.object(zwave, '_LOGGER') as mock_logger: + mock_network.nodes = { + node.node_id: node + } + yield from hass.services.async_call( + zwave.DOMAIN, zwave.SERVICE_GET_USERCODE, { + const.ATTR_NODE_ID: node.node_id, + zwave.ATTR_CODE_SLOT: 1, + }) + yield from hass.async_block_till_done() + + # This service only seems to write to the log + assert mock_logger.info.called + assert len(mock_logger.info.mock_calls) == 1 + assert mock_logger.info.mock_calls[0][1][2] == '1234' + + +@asyncio.coroutine +def test_lock_clear_usercode_service(hass, mock_openzwave): + """Test the zwave lock clear_usercode service.""" + node = MockNode(node_id=12) + value0 = MockValue(data=None, node=node, index=0) + value1 = MockValue(data='123', node=node, index=1) + yield from zwave.async_setup_platform( + hass, {}, MagicMock()) + + node.get_values.return_value = { + value0.value_id: value0, + value1.value_id: value1, + } + + with patch.object(zwave.zwave, 'NETWORK') as mock_network: + mock_network.nodes = { + node.node_id: node + } + yield from hass.services.async_call( + zwave.DOMAIN, zwave.SERVICE_CLEAR_USERCODE, { + const.ATTR_NODE_ID: node.node_id, + zwave.ATTR_CODE_SLOT: 1 + }) + yield from hass.async_block_till_done() + + assert value1.data == '\0\0\0' diff --git a/tests/mock/zwave.py b/tests/mock/zwave.py index 62678dbafff..361a29562fc 100644 --- a/tests/mock/zwave.py +++ b/tests/mock/zwave.py @@ -67,7 +67,7 @@ class MockValue(MagicMock): self.data_items = data_items self.node = node self.instance = instance - self.index = 0 + self.index = index self.command_class = command_class self.units = units self.type = type