From 168e1f0e2d0fb43a3bb96c935b36a014f04fd1fc Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Fri, 2 Mar 2018 00:20:02 +0100 Subject: [PATCH] Improved Homekit tests (#12800) * Added test for temperature fahrenheit * Restructured tests to use more mocks * Rearanged homekit constants * Improved 'test_homekit_class' * Added import statements * Fix Pylint Test errors --- homeassistant/components/homekit/__init__.py | 6 +- .../components/homekit/accessories.py | 17 +- homeassistant/components/homekit/const.py | 23 +-- homeassistant/components/homekit/covers.py | 3 + homeassistant/components/homekit/sensors.py | 1 + tests/components/homekit/test_accessories.py | 165 ++++++++++++++++++ tests/components/homekit/test_covers.py | 16 +- .../homekit/test_get_accessories.py | 21 ++- tests/components/homekit/test_homekit.py | 113 ++++++------ tests/components/homekit/test_sensors.py | 16 +- tests/mock/homekit.py | 133 ++++++++++++++ 11 files changed, 420 insertions(+), 94 deletions(-) create mode 100644 tests/components/homekit/test_accessories.py create mode 100644 tests/mock/homekit.py diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 77c69c14596..40d43e2e14c 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -64,12 +64,10 @@ def async_setup(hass, config): def import_types(): - """Import all types from files in the HomeKit dir.""" + """Import all types from files in the HomeKit directory.""" _LOGGER.debug("Import type files.") # pylint: disable=unused-variable - from .covers import Window # noqa F401 - # pylint: disable=unused-variable - from .sensors import TemperatureSensor # noqa F401 + from . import covers, sensors # noqa F401 def get_accessory(hass, state): diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index f2ad6067258..689bcb3377c 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -4,7 +4,7 @@ import logging from pyhap.accessory import Accessory, Bridge, Category from .const import ( - SERV_ACCESSORY_INFO, MANUFACTURER, + SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE, MANUFACTURER, CHAR_MODEL, CHAR_MANUFACTURER, CHAR_SERIAL_NUMBER) @@ -46,17 +46,24 @@ def override_properties(char, new_properties): class HomeAccessory(Accessory): """Class to extend the Accessory class.""" - def __init__(self, display_name, model, category='OTHER'): + def __init__(self, display_name, model, category='OTHER', **kwargs): """Initialize a Accessory object.""" - super().__init__(display_name) + super().__init__(display_name, **kwargs) set_accessory_info(self, model) self.category = getattr(Category, category, Category.OTHER) + def _set_services(self): + add_preload_service(self, SERV_ACCESSORY_INFO) + class HomeBridge(Bridge): """Class to extend the Bridge class.""" - def __init__(self, display_name, model, pincode): + def __init__(self, display_name, model, pincode, **kwargs): """Initialize a Bridge object.""" - super().__init__(display_name, pincode=pincode) + super().__init__(display_name, pincode=pincode, **kwargs) set_accessory_info(self, model) + + def _set_services(self): + add_preload_service(self, SERV_ACCESSORY_INFO) + add_preload_service(self, SERV_BRIDGING_STATE) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index f514308c823..5201e21608a 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -1,21 +1,24 @@ """Constants used be the HomeKit component.""" MANUFACTURER = 'HomeAssistant' -# Service: AccessoryInfomation +# Services SERV_ACCESSORY_INFO = 'AccessoryInformation' -CHAR_MODEL = 'Model' -CHAR_MANUFACTURER = 'Manufacturer' -CHAR_SERIAL_NUMBER = 'SerialNumber' - -# Service: TemperatureSensor +SERV_BRIDGING_STATE = 'BridgingState' SERV_TEMPERATURE_SENSOR = 'TemperatureSensor' -CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature' - -# Service: WindowCovering SERV_WINDOW_COVERING = 'WindowCovering' + +# Characteristics +CHAR_ACC_IDENTIFIER = 'AccessoryIdentifier' +CHAR_CATEGORY = 'Category' CHAR_CURRENT_POSITION = 'CurrentPosition' -CHAR_TARGET_POSITION = 'TargetPosition' +CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature' +CHAR_LINK_QUALITY = 'LinkQuality' +CHAR_MANUFACTURER = 'Manufacturer' +CHAR_MODEL = 'Model' CHAR_POSITION_STATE = 'PositionState' +CHAR_REACHABLE = 'Reachable' +CHAR_SERIAL_NUMBER = 'SerialNumber' +CHAR_TARGET_POSITION = 'TargetPosition' # Properties PROP_CELSIUS = {'minValue': -273, 'maxValue': 999} diff --git a/homeassistant/components/homekit/covers.py b/homeassistant/components/homekit/covers.py index e9fc3b08d76..47713f6c630 100644 --- a/homeassistant/components/homekit/covers.py +++ b/homeassistant/components/homekit/covers.py @@ -38,6 +38,9 @@ class Window(HomeAccessory): get_characteristic(CHAR_TARGET_POSITION) self.char_position_state = self.serv_cover. \ get_characteristic(CHAR_POSITION_STATE) + self.char_current_position.value = 0 + self.char_target_position.value = 0 + self.char_position_state.value = 0 self.char_target_position.setter_callback = self.move_cover diff --git a/homeassistant/components/homekit/sensors.py b/homeassistant/components/homekit/sensors.py index 968f6834b1d..40f97ae3ef7 100644 --- a/homeassistant/components/homekit/sensors.py +++ b/homeassistant/components/homekit/sensors.py @@ -47,6 +47,7 @@ class TemperatureSensor(HomeAccessory): self.char_temp = self.serv_temp. \ get_characteristic(CHAR_CURRENT_TEMPERATURE) override_properties(self.char_temp, PROP_CELSIUS) + self.char_temp.value = 0 self.unit = None def run(self): diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py new file mode 100644 index 00000000000..a45aa82d981 --- /dev/null +++ b/tests/components/homekit/test_accessories.py @@ -0,0 +1,165 @@ +"""Test all functions related to the basic accessory implementation. + +This includes tests for all mock object types. +""" + +from unittest.mock import patch + +# pylint: disable=unused-import +from pyhap.loader import get_serv_loader, get_char_loader # noqa F401 + +from homeassistant.components.homekit.accessories import ( + set_accessory_info, add_preload_service, override_properties, + HomeAccessory, HomeBridge) +from homeassistant.components.homekit.const import ( + SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE, + CHAR_MODEL, CHAR_MANUFACTURER, CHAR_SERIAL_NUMBER) + +from tests.mock.homekit import ( + get_patch_paths, mock_preload_service, + MockTypeLoader, MockAccessory, MockService, MockChar) + +PATH_SERV = 'pyhap.loader.get_serv_loader' +PATH_CHAR = 'pyhap.loader.get_char_loader' +PATH_ACC, _ = get_patch_paths() + + +@patch(PATH_CHAR, return_value=MockTypeLoader('char')) +@patch(PATH_SERV, return_value=MockTypeLoader('service')) +def test_add_preload_service(mock_serv, mock_char): + """Test method add_preload_service. + + The methods 'get_serv_loader' and 'get_char_loader' are mocked. + """ + acc = MockAccessory('Accessory') + serv = add_preload_service(acc, 'TestService', + ['TestChar', 'TestChar2'], + ['TestOptChar', 'TestOptChar2']) + + assert serv.display_name == 'TestService' + assert len(serv.characteristics) == 2 + assert len(serv.opt_characteristics) == 2 + + acc.services = [] + serv = add_preload_service(acc, 'TestService') + + assert not serv.characteristics + assert not serv.opt_characteristics + + acc.services = [] + serv = add_preload_service(acc, 'TestService', + 'TestChar', 'TestOptChar') + + assert len(serv.characteristics) == 1 + assert len(serv.opt_characteristics) == 1 + + assert serv.characteristics[0].display_name == 'TestChar' + assert serv.opt_characteristics[0].display_name == 'TestOptChar' + + +def test_override_properties(): + """Test override of characteristic properties with MockChar.""" + char = MockChar('TestChar') + new_prop = {1: 'Test', 2: 'Demo'} + override_properties(char, new_prop) + + assert char.properties == new_prop + + +def test_set_accessory_info(): + """Test setting of basic accessory information with MockAccessory.""" + acc = MockAccessory('Accessory') + set_accessory_info(acc, 'model', 'manufacturer', '0000') + + assert len(acc.services) == 1 + serv = acc.services[0] + + assert serv.display_name == SERV_ACCESSORY_INFO + assert len(serv.characteristics) == 3 + chars = serv.characteristics + + assert chars[0].display_name == CHAR_MODEL + assert chars[0].value == 'model' + assert chars[1].display_name == CHAR_MANUFACTURER + assert chars[1].value == 'manufacturer' + assert chars[2].display_name == CHAR_SERIAL_NUMBER + assert chars[2].value == '0000' + + +@patch(PATH_ACC, side_effect=mock_preload_service) +def test_home_accessory(mock_pre_serv): + """Test initializing a HomeAccessory object.""" + acc = HomeAccessory('TestAccessory', 'test.accessory', 'WINDOW') + + assert acc.display_name == 'TestAccessory' + assert acc.category == 13 # Category.WINDOW + assert len(acc.services) == 1 + + serv = acc.services[0] + assert serv.display_name == SERV_ACCESSORY_INFO + char_model = serv.get_characteristic(CHAR_MODEL) + assert char_model.get_value() == 'test.accessory' + + +@patch(PATH_ACC, side_effect=mock_preload_service) +def test_home_bridge(mock_pre_serv): + """Test initializing a HomeBridge object.""" + bridge = HomeBridge('TestBridge', 'test.bridge', b'123-45-678') + + assert bridge.display_name == 'TestBridge' + assert bridge.pincode == b'123-45-678' + assert len(bridge.services) == 2 + + assert bridge.services[0].display_name == SERV_ACCESSORY_INFO + assert bridge.services[1].display_name == SERV_BRIDGING_STATE + + char_model = bridge.services[0].get_characteristic(CHAR_MODEL) + assert char_model.get_value() == 'test.bridge' + + +def test_mock_accessory(): + """Test attributes and functions of a MockAccessory.""" + acc = MockAccessory('TestAcc') + serv = MockService('TestServ') + acc.add_service(serv) + + assert acc.display_name == 'TestAcc' + assert len(acc.services) == 1 + + assert acc.get_service('TestServ') == serv + assert acc.get_service('NewServ').display_name == 'NewServ' + assert len(acc.services) == 2 + + +def test_mock_service(): + """Test attributes and functions of a MockService.""" + serv = MockService('TestServ') + char = MockChar('TestChar') + opt_char = MockChar('TestOptChar') + serv.add_characteristic(char) + serv.add_opt_characteristic(opt_char) + + assert serv.display_name == 'TestServ' + assert len(serv.characteristics) == 1 + assert len(serv.opt_characteristics) == 1 + + assert serv.get_characteristic('TestChar') == char + assert serv.get_characteristic('TestOptChar') == opt_char + assert serv.get_characteristic('NewChar').display_name == 'NewChar' + assert len(serv.characteristics) == 2 + + +def test_mock_char(): + """Test attributes and functions of a MockChar.""" + def callback_method(value): + """Provide a callback options for 'set_value' method.""" + assert value == 'With callback' + + char = MockChar('TestChar') + char.set_value('Value') + + assert char.display_name == 'TestChar' + assert char.get_value() == 'Value' + + char.setter_callback = callback_method + char.set_value('With callback') diff --git a/tests/components/homekit/test_covers.py b/tests/components/homekit/test_covers.py index f665a92682c..fe0ede5d8fb 100644 --- a/tests/components/homekit/test_covers.py +++ b/tests/components/homekit/test_covers.py @@ -1,5 +1,6 @@ """Test different accessory types: Covers.""" import unittest +from unittest.mock import patch from homeassistant.core import callback from homeassistant.components.cover import ( @@ -10,6 +11,9 @@ from homeassistant.const import ( ATTR_SERVICE, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE) from tests.common import get_test_home_assistant +from tests.mock.homekit import get_patch_paths, mock_preload_service + +PATH_ACC, PATH_FILE = get_patch_paths('covers') class TestHomekitSensors(unittest.TestCase): @@ -35,13 +39,14 @@ class TestHomekitSensors(unittest.TestCase): """Test if accessory and HA are updated accordingly.""" window_cover = 'cover.window' - acc = Window(self.hass, window_cover, 'Cover') - acc.run() + with patch(PATH_ACC, side_effect=mock_preload_service): + with patch(PATH_FILE, side_effect=mock_preload_service): + acc = Window(self.hass, window_cover, 'Cover') + acc.run() self.assertEqual(acc.char_current_position.value, 0) self.assertEqual(acc.char_target_position.value, 0) - # Temporarily disabled due to bug in HAP-python==1.15 with py3.5 - # self.assertEqual(acc.char_position_state.value, 0) + self.assertEqual(acc.char_position_state.value, 0) self.hass.states.set(window_cover, STATE_UNKNOWN, {ATTR_CURRENT_POSITION: None}) @@ -49,8 +54,7 @@ class TestHomekitSensors(unittest.TestCase): self.assertEqual(acc.char_current_position.value, 0) self.assertEqual(acc.char_target_position.value, 0) - # Temporarily disabled due to bug in HAP-python==1.15 with py3.5 - # self.assertEqual(acc.char_position_state.value, 0) + self.assertEqual(acc.char_position_state.value, 0) self.hass.states.set(window_cover, STATE_OPEN, {ATTR_CURRENT_POSITION: 50}) diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index e20e87871b8..6e49674a7b9 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -6,7 +6,7 @@ from homeassistant.components.homekit import ( TYPES, get_accessory, import_types) from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, ATTR_SUPPORTED_FEATURES, - TEMP_CELSIUS, STATE_UNKNOWN) + TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_UNKNOWN) def test_import_types(): @@ -26,21 +26,32 @@ def test_component_not_supported(): assert True if get_accessory(None, state) is None else False -def test_sensor_temperatur_celsius(): - """Test temperature sensor with celsius as unit.""" +def test_sensor_temperature_celsius(): + """Test temperature sensor with Celsius as unit.""" mock_type = MagicMock() with patch.dict(TYPES, {'TemperatureSensor': mock_type}): - state = State('sensor.temperatur', '23', + state = State('sensor.temperature', '23', {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) get_accessory(None, state) assert len(mock_type.mock_calls) == 1 +# pylint: disable=invalid-name +def test_sensor_temperature_fahrenheit(): + """Test temperature sensor with Fahrenheit as unit.""" + mock_type = MagicMock() + with patch.dict(TYPES, {'TemperatureSensor': mock_type}): + state = State('sensor.temperature', '74', + {ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT}) + get_accessory(None, state) + assert len(mock_type.mock_calls) == 1 + + def test_cover_set_position(): """Test cover with support for set_cover_position.""" mock_type = MagicMock() with patch.dict(TYPES, {'Window': mock_type}): - state = State('cover.setposition', 'open', + state = State('cover.set_position', 'open', {ATTR_SUPPORTED_FEATURES: 4}) get_accessory(None, state) assert len(mock_type.mock_calls) == 1 diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 2ab284e829a..58c197e69ec 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -1,20 +1,25 @@ """Tests for the HomeKit component.""" import unittest -from unittest.mock import call, patch +from unittest.mock import call, patch, ANY import voluptuous as vol +# pylint: disable=unused-import +from pyhap.accessory_driver import AccessoryDriver # noqa F401 + from homeassistant import setup from homeassistant.core import Event from homeassistant.components.homekit import ( - CONF_PIN_CODE, BRIDGE_NAME, HOMEKIT_FILE, HomeKit, valid_pin) + CONF_PIN_CODE, HOMEKIT_FILE, HomeKit, valid_pin) from homeassistant.const import ( CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from tests.common import get_test_home_assistant +from tests.mock.homekit import get_patch_paths, PATH_HOMEKIT -HOMEKIT_PATH = 'homeassistant.components.homekit' +PATH_ACC, _ = get_patch_paths() +IP_ADDRESS = '127.0.0.1' CONFIG_MIN = {'homekit': {}} CONFIG = { @@ -36,39 +41,6 @@ class TestHomeKit(unittest.TestCase): """Stop down everything that was started.""" self.hass.stop() - @patch(HOMEKIT_PATH + '.HomeKit.start_driver') - @patch(HOMEKIT_PATH + '.HomeKit.setup_bridge') - @patch(HOMEKIT_PATH + '.HomeKit.__init__') - def test_setup_min(self, mock_homekit, mock_setup_bridge, - mock_start_driver): - """Test async_setup with minimal config option.""" - mock_homekit.return_value = None - - self.assertTrue(setup.setup_component( - self.hass, 'homekit', CONFIG_MIN)) - - mock_homekit.assert_called_once_with(self.hass, 51826) - mock_setup_bridge.assert_called_with(b'123-45-678') - mock_start_driver.assert_not_called() - - self.hass.start() - self.hass.block_till_done() - self.assertEqual(mock_start_driver.call_count, 1) - - @patch(HOMEKIT_PATH + '.HomeKit.start_driver') - @patch(HOMEKIT_PATH + '.HomeKit.setup_bridge') - @patch(HOMEKIT_PATH + '.HomeKit.__init__') - def test_setup_parameters(self, mock_homekit, mock_setup_bridge, - mock_start_driver): - """Test async_setup with full config option.""" - mock_homekit.return_value = None - - self.assertTrue(setup.setup_component( - self.hass, 'homekit', CONFIG)) - - mock_homekit.assert_called_once_with(self.hass, 11111) - mock_setup_bridge.assert_called_with(b'987-65-432') - def test_validate_pincode(self): """Test async_setup with invalid config option.""" schema = vol.Schema(valid_pin) @@ -80,45 +52,64 @@ class TestHomeKit(unittest.TestCase): for value in ('123-45-678', '234-56-789'): self.assertTrue(schema(value)) + @patch(PATH_HOMEKIT + '.HomeKit') + def test_setup_min(self, mock_homekit): + """Test async_setup with minimal config option.""" + self.assertTrue(setup.setup_component( + self.hass, 'homekit', CONFIG_MIN)) + + self.assertEqual(mock_homekit.mock_calls, + [call(self.hass, 51826), + call().setup_bridge(b'123-45-678')]) + mock_homekit.reset_mock() + + self.hass.bus.fire(EVENT_HOMEASSISTANT_START) + self.hass.block_till_done() + + self.assertEqual(mock_homekit.mock_calls, + [call().start_driver(ANY)]) + + @patch(PATH_HOMEKIT + '.HomeKit') + def test_setup_parameters(self, mock_homekit): + """Test async_setup with full config option.""" + self.assertTrue(setup.setup_component( + self.hass, 'homekit', CONFIG)) + + self.assertEqual(mock_homekit.mock_calls, + [call(self.hass, 11111), + call().setup_bridge(b'987-65-432')]) + @patch('pyhap.accessory_driver.AccessoryDriver') - @patch('pyhap.accessory.Bridge.add_accessory') - @patch(HOMEKIT_PATH + '.import_types') - @patch(HOMEKIT_PATH + '.get_accessory') - def test_homekit_pyhap_interaction( - self, mock_get_accessory, mock_import_types, - mock_add_accessory, mock_acc_driver): + def test_homekit_class(self, mock_acc_driver): """Test interaction between the HomeKit class and pyhap.""" - mock_get_accessory.side_effect = ['TemperatureSensor', 'Window'] - - homekit = HomeKit(self.hass, 51826) - homekit.setup_bridge(b'123-45-678') - - self.assertEqual(homekit.bridge.display_name, BRIDGE_NAME) + with patch(PATH_HOMEKIT + '.accessories.HomeBridge') as mock_bridge: + homekit = HomeKit(self.hass, 51826) + homekit.setup_bridge(b'123-45-678') + mock_bridge.reset_mock() self.hass.states.set('demo.demo1', 'on') self.hass.states.set('demo.demo2', 'off') - self.hass.start() - self.hass.block_till_done() - - with patch('homeassistant.util.get_local_ip', - return_value='127.0.0.1'): + with patch(PATH_HOMEKIT + '.get_accessory') as mock_get_acc, \ + patch(PATH_HOMEKIT + '.import_types') as mock_import_types, \ + patch('homeassistant.util.get_local_ip') as mock_ip: + mock_get_acc.side_effect = ['TempSensor', 'Window'] + mock_ip.return_value = IP_ADDRESS homekit.start_driver(Event(EVENT_HOMEASSISTANT_START)) - ip_address = '127.0.0.1' path = self.hass.config.path(HOMEKIT_FILE) - self.assertEqual(mock_get_accessory.call_count, 2) self.assertEqual(mock_import_types.call_count, 1) + self.assertEqual(mock_get_acc.call_count, 2) + self.assertEqual(mock_bridge.mock_calls, + [call().add_accessory('TempSensor'), + call().add_accessory('Window')]) self.assertEqual(mock_acc_driver.mock_calls, - [call(homekit.bridge, 51826, ip_address, path), + [call(homekit.bridge, 51826, IP_ADDRESS, path), call().start()]) - - self.assertEqual(mock_add_accessory.mock_calls, - [call('TemperatureSensor'), call('Window')]) + mock_acc_driver.reset_mock() self.hass.bus.fire(EVENT_HOMEASSISTANT_STOP) self.hass.block_till_done() - self.assertEqual(mock_acc_driver.mock_calls[2], call().stop()) - self.assertEqual(len(mock_acc_driver.mock_calls), 3) + self.assertEqual(mock_acc_driver.mock_calls, [call().stop()]) diff --git a/tests/components/homekit/test_sensors.py b/tests/components/homekit/test_sensors.py index 86a832f570a..4698c363503 100644 --- a/tests/components/homekit/test_sensors.py +++ b/tests/components/homekit/test_sensors.py @@ -1,12 +1,17 @@ """Test different accessory types: Sensors.""" import unittest +from unittest.mock import patch +from homeassistant.components.homekit.const import PROP_CELSIUS from homeassistant.components.homekit.sensors import ( TemperatureSensor, calc_temperature) from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_UNKNOWN) from tests.common import get_test_home_assistant +from tests.mock.homekit import get_patch_paths, mock_preload_service + +PATH_ACC, PATH_FILE = get_patch_paths('sensors') def test_calc_temperature(): @@ -27,19 +32,24 @@ class TestHomekitSensors(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() + get_patch_paths('sensors') def tearDown(self): """Stop down everything that was started.""" self.hass.stop() - def test_temperature_celsius(self): + def test_temperature(self): """Test if accessory is updated after state change.""" temperature_sensor = 'sensor.temperature' - acc = TemperatureSensor(self.hass, temperature_sensor, 'Temperature') - acc.run() + with patch(PATH_ACC, side_effect=mock_preload_service): + with patch(PATH_FILE, side_effect=mock_preload_service): + acc = TemperatureSensor(self.hass, temperature_sensor, + 'Temperature') + acc.run() self.assertEqual(acc.char_temp.value, 0.0) + self.assertEqual(acc.char_temp.properties, PROP_CELSIUS) self.hass.states.set(temperature_sensor, STATE_UNKNOWN, {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) diff --git a/tests/mock/homekit.py b/tests/mock/homekit.py new file mode 100644 index 00000000000..2872fa59f19 --- /dev/null +++ b/tests/mock/homekit.py @@ -0,0 +1,133 @@ +"""Basic mock functions and objects related to the HomeKit component.""" +PATH_HOMEKIT = 'homeassistant.components.homekit' + + +def get_patch_paths(name=None): + """Return paths to mock 'add_preload_service'.""" + path_acc = PATH_HOMEKIT + '.accessories.add_preload_service' + path_file = PATH_HOMEKIT + '.' + str(name) + '.add_preload_service' + return (path_acc, path_file) + + +def mock_preload_service(acc, service, chars=None, opt_chars=None): + """Mock alternative for function 'add_preload_service'.""" + service = MockService(service) + if chars: + chars = chars if isinstance(chars, list) else [chars] + for char_name in chars: + service.add_characteristic(char_name) + if opt_chars: + opt_chars = opt_chars if isinstance(opt_chars, list) else [opt_chars] + for opt_char_name in opt_chars: + service.add_characteristic(opt_char_name) + acc.add_service(service) + return service + + +class MockAccessory(): + """Define all attributes and methods for a MockAccessory.""" + + def __init__(self, name): + """Initialize a MockAccessory object.""" + self.display_name = name + self.services = [] + + def __repr__(self): + """Return a representation of a MockAccessory. Use for debugging.""" + serv_list = [serv.display_name for serv in self.services] + return "".format( + self.display_name, serv_list) + + def add_service(self, service): + """Add service to list of services.""" + self.services.append(service) + + def get_service(self, name): + """Retrieve service from service list or return new MockService.""" + for serv in self.services: + if serv.display_name == name: + return serv + serv = MockService(name) + self.add_service(serv) + return serv + + +class MockService(): + """Define all attributes and methods for a MockService.""" + + def __init__(self, name): + """Initialize a MockService object.""" + self.characteristics = [] + self.opt_characteristics = [] + self.display_name = name + + def __repr__(self): + """Return a representation of a MockService. Use for debugging.""" + char_list = [char.display_name for char in self.characteristics] + opt_char_list = [ + char.display_name for char in self.opt_characteristics] + return "".format( + self.display_name, char_list, opt_char_list) + + def add_characteristic(self, char): + """Add characteristic to char list.""" + self.characteristics.append(char) + + def add_opt_characteristic(self, char): + """Add characteristic to opt_char list.""" + self.opt_characteristics.append(char) + + def get_characteristic(self, name): + """Get char for char lists or return new MockChar.""" + for char in self.characteristics: + if char.display_name == name: + return char + for char in self.opt_characteristics: + if char.display_name == name: + return char + char = MockChar(name) + self.add_characteristic(char) + return char + + +class MockChar(): + """Define all attributes and methods for a MockChar.""" + + def __init__(self, name): + """Initialize a MockChar object.""" + self.display_name = name + self.properties = {} + self.value = None + self.type_id = None + self.setter_callback = None + + def __repr__(self): + """Return a representation of a MockChar. Use for debugging.""" + return "".format( + self.display_name, self.value) + + def set_value(self, value, should_notify=True, should_callback=True): + """Set value of char.""" + self.value = value + if self.setter_callback is not None and should_callback: + # pylint: disable=not-callable + self.setter_callback(value) + + def get_value(self): + """Get char value.""" + return self.value + + +class MockTypeLoader(): + """Define all attributes and methods for a MockTypeLoader.""" + + def __init__(self, class_type): + """Initialize a MockTypeLoader object.""" + self.class_type = class_type + + def get(self, name): + """Return a MockService or MockChar object.""" + if self.class_type == 'service': + return MockService(name) + elif self.class_type == 'char': + return MockChar(name)