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
This commit is contained in:
cdce8p 2018-03-02 00:20:02 +01:00 committed by Paulus Schoutsen
parent d3386907a4
commit 168e1f0e2d
11 changed files with 420 additions and 94 deletions

View file

@ -64,12 +64,10 @@ def async_setup(hass, config):
def import_types(): 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.") _LOGGER.debug("Import type files.")
# pylint: disable=unused-variable # pylint: disable=unused-variable
from .covers import Window # noqa F401 from . import covers, sensors # noqa F401
# pylint: disable=unused-variable
from .sensors import TemperatureSensor # noqa F401
def get_accessory(hass, state): def get_accessory(hass, state):

View file

@ -4,7 +4,7 @@ import logging
from pyhap.accessory import Accessory, Bridge, Category from pyhap.accessory import Accessory, Bridge, Category
from .const import ( from .const import (
SERV_ACCESSORY_INFO, MANUFACTURER, SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE, MANUFACTURER,
CHAR_MODEL, CHAR_MANUFACTURER, CHAR_SERIAL_NUMBER) CHAR_MODEL, CHAR_MANUFACTURER, CHAR_SERIAL_NUMBER)
@ -46,17 +46,24 @@ def override_properties(char, new_properties):
class HomeAccessory(Accessory): class HomeAccessory(Accessory):
"""Class to extend the Accessory class.""" """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.""" """Initialize a Accessory object."""
super().__init__(display_name) super().__init__(display_name, **kwargs)
set_accessory_info(self, model) set_accessory_info(self, model)
self.category = getattr(Category, category, Category.OTHER) self.category = getattr(Category, category, Category.OTHER)
def _set_services(self):
add_preload_service(self, SERV_ACCESSORY_INFO)
class HomeBridge(Bridge): class HomeBridge(Bridge):
"""Class to extend the Bridge class.""" """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.""" """Initialize a Bridge object."""
super().__init__(display_name, pincode=pincode) super().__init__(display_name, pincode=pincode, **kwargs)
set_accessory_info(self, model) set_accessory_info(self, model)
def _set_services(self):
add_preload_service(self, SERV_ACCESSORY_INFO)
add_preload_service(self, SERV_BRIDGING_STATE)

View file

@ -1,21 +1,24 @@
"""Constants used be the HomeKit component.""" """Constants used be the HomeKit component."""
MANUFACTURER = 'HomeAssistant' MANUFACTURER = 'HomeAssistant'
# Service: AccessoryInfomation # Services
SERV_ACCESSORY_INFO = 'AccessoryInformation' SERV_ACCESSORY_INFO = 'AccessoryInformation'
CHAR_MODEL = 'Model' SERV_BRIDGING_STATE = 'BridgingState'
CHAR_MANUFACTURER = 'Manufacturer'
CHAR_SERIAL_NUMBER = 'SerialNumber'
# Service: TemperatureSensor
SERV_TEMPERATURE_SENSOR = 'TemperatureSensor' SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature'
# Service: WindowCovering
SERV_WINDOW_COVERING = 'WindowCovering' SERV_WINDOW_COVERING = 'WindowCovering'
# Characteristics
CHAR_ACC_IDENTIFIER = 'AccessoryIdentifier'
CHAR_CATEGORY = 'Category'
CHAR_CURRENT_POSITION = 'CurrentPosition' 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_POSITION_STATE = 'PositionState'
CHAR_REACHABLE = 'Reachable'
CHAR_SERIAL_NUMBER = 'SerialNumber'
CHAR_TARGET_POSITION = 'TargetPosition'
# Properties # Properties
PROP_CELSIUS = {'minValue': -273, 'maxValue': 999} PROP_CELSIUS = {'minValue': -273, 'maxValue': 999}

View file

@ -38,6 +38,9 @@ class Window(HomeAccessory):
get_characteristic(CHAR_TARGET_POSITION) get_characteristic(CHAR_TARGET_POSITION)
self.char_position_state = self.serv_cover. \ self.char_position_state = self.serv_cover. \
get_characteristic(CHAR_POSITION_STATE) 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 self.char_target_position.setter_callback = self.move_cover

View file

@ -47,6 +47,7 @@ class TemperatureSensor(HomeAccessory):
self.char_temp = self.serv_temp. \ self.char_temp = self.serv_temp. \
get_characteristic(CHAR_CURRENT_TEMPERATURE) get_characteristic(CHAR_CURRENT_TEMPERATURE)
override_properties(self.char_temp, PROP_CELSIUS) override_properties(self.char_temp, PROP_CELSIUS)
self.char_temp.value = 0
self.unit = None self.unit = None
def run(self): def run(self):

View file

@ -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')

View file

@ -1,5 +1,6 @@
"""Test different accessory types: Covers.""" """Test different accessory types: Covers."""
import unittest import unittest
from unittest.mock import patch
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components.cover import ( from homeassistant.components.cover import (
@ -10,6 +11,9 @@ from homeassistant.const import (
ATTR_SERVICE, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE) ATTR_SERVICE, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE)
from tests.common import get_test_home_assistant 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): class TestHomekitSensors(unittest.TestCase):
@ -35,13 +39,14 @@ class TestHomekitSensors(unittest.TestCase):
"""Test if accessory and HA are updated accordingly.""" """Test if accessory and HA are updated accordingly."""
window_cover = 'cover.window' window_cover = 'cover.window'
acc = Window(self.hass, window_cover, 'Cover') with patch(PATH_ACC, side_effect=mock_preload_service):
acc.run() 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_current_position.value, 0)
self.assertEqual(acc.char_target_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, self.hass.states.set(window_cover, STATE_UNKNOWN,
{ATTR_CURRENT_POSITION: None}) {ATTR_CURRENT_POSITION: None})
@ -49,8 +54,7 @@ class TestHomekitSensors(unittest.TestCase):
self.assertEqual(acc.char_current_position.value, 0) self.assertEqual(acc.char_current_position.value, 0)
self.assertEqual(acc.char_target_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, self.hass.states.set(window_cover, STATE_OPEN,
{ATTR_CURRENT_POSITION: 50}) {ATTR_CURRENT_POSITION: 50})

View file

@ -6,7 +6,7 @@ from homeassistant.components.homekit import (
TYPES, get_accessory, import_types) TYPES, get_accessory, import_types)
from homeassistant.const import ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT, ATTR_SUPPORTED_FEATURES,
TEMP_CELSIUS, STATE_UNKNOWN) TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_UNKNOWN)
def test_import_types(): def test_import_types():
@ -26,21 +26,32 @@ def test_component_not_supported():
assert True if get_accessory(None, state) is None else False assert True if get_accessory(None, state) is None else False
def test_sensor_temperatur_celsius(): def test_sensor_temperature_celsius():
"""Test temperature sensor with celsius as unit.""" """Test temperature sensor with Celsius as unit."""
mock_type = MagicMock() mock_type = MagicMock()
with patch.dict(TYPES, {'TemperatureSensor': mock_type}): with patch.dict(TYPES, {'TemperatureSensor': mock_type}):
state = State('sensor.temperatur', '23', state = State('sensor.temperature', '23',
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})
get_accessory(None, state) get_accessory(None, state)
assert len(mock_type.mock_calls) == 1 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(): def test_cover_set_position():
"""Test cover with support for set_cover_position.""" """Test cover with support for set_cover_position."""
mock_type = MagicMock() mock_type = MagicMock()
with patch.dict(TYPES, {'Window': mock_type}): with patch.dict(TYPES, {'Window': mock_type}):
state = State('cover.setposition', 'open', state = State('cover.set_position', 'open',
{ATTR_SUPPORTED_FEATURES: 4}) {ATTR_SUPPORTED_FEATURES: 4})
get_accessory(None, state) get_accessory(None, state)
assert len(mock_type.mock_calls) == 1 assert len(mock_type.mock_calls) == 1

View file

@ -1,20 +1,25 @@
"""Tests for the HomeKit component.""" """Tests for the HomeKit component."""
import unittest import unittest
from unittest.mock import call, patch from unittest.mock import call, patch, ANY
import voluptuous as vol import voluptuous as vol
# pylint: disable=unused-import
from pyhap.accessory_driver import AccessoryDriver # noqa F401
from homeassistant import setup from homeassistant import setup
from homeassistant.core import Event from homeassistant.core import Event
from homeassistant.components.homekit import ( 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 ( from homeassistant.const import (
CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from tests.common import get_test_home_assistant 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_MIN = {'homekit': {}}
CONFIG = { CONFIG = {
@ -36,39 +41,6 @@ class TestHomeKit(unittest.TestCase):
"""Stop down everything that was started.""" """Stop down everything that was started."""
self.hass.stop() 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): def test_validate_pincode(self):
"""Test async_setup with invalid config option.""" """Test async_setup with invalid config option."""
schema = vol.Schema(valid_pin) schema = vol.Schema(valid_pin)
@ -80,45 +52,64 @@ class TestHomeKit(unittest.TestCase):
for value in ('123-45-678', '234-56-789'): for value in ('123-45-678', '234-56-789'):
self.assertTrue(schema(value)) 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_driver.AccessoryDriver')
@patch('pyhap.accessory.Bridge.add_accessory') def test_homekit_class(self, mock_acc_driver):
@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):
"""Test interaction between the HomeKit class and pyhap.""" """Test interaction between the HomeKit class and pyhap."""
mock_get_accessory.side_effect = ['TemperatureSensor', 'Window'] with patch(PATH_HOMEKIT + '.accessories.HomeBridge') as mock_bridge:
homekit = HomeKit(self.hass, 51826)
homekit = HomeKit(self.hass, 51826) homekit.setup_bridge(b'123-45-678')
homekit.setup_bridge(b'123-45-678')
self.assertEqual(homekit.bridge.display_name, BRIDGE_NAME)
mock_bridge.reset_mock()
self.hass.states.set('demo.demo1', 'on') self.hass.states.set('demo.demo1', 'on')
self.hass.states.set('demo.demo2', 'off') self.hass.states.set('demo.demo2', 'off')
self.hass.start() with patch(PATH_HOMEKIT + '.get_accessory') as mock_get_acc, \
self.hass.block_till_done() patch(PATH_HOMEKIT + '.import_types') as mock_import_types, \
patch('homeassistant.util.get_local_ip') as mock_ip:
with patch('homeassistant.util.get_local_ip', mock_get_acc.side_effect = ['TempSensor', 'Window']
return_value='127.0.0.1'): mock_ip.return_value = IP_ADDRESS
homekit.start_driver(Event(EVENT_HOMEASSISTANT_START)) homekit.start_driver(Event(EVENT_HOMEASSISTANT_START))
ip_address = '127.0.0.1'
path = self.hass.config.path(HOMEKIT_FILE) 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_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, self.assertEqual(mock_acc_driver.mock_calls,
[call(homekit.bridge, 51826, ip_address, path), [call(homekit.bridge, 51826, IP_ADDRESS, path),
call().start()]) call().start()])
mock_acc_driver.reset_mock()
self.assertEqual(mock_add_accessory.mock_calls,
[call('TemperatureSensor'), call('Window')])
self.hass.bus.fire(EVENT_HOMEASSISTANT_STOP) self.hass.bus.fire(EVENT_HOMEASSISTANT_STOP)
self.hass.block_till_done() self.hass.block_till_done()
self.assertEqual(mock_acc_driver.mock_calls[2], call().stop()) self.assertEqual(mock_acc_driver.mock_calls, [call().stop()])
self.assertEqual(len(mock_acc_driver.mock_calls), 3)

View file

@ -1,12 +1,17 @@
"""Test different accessory types: Sensors.""" """Test different accessory types: Sensors."""
import unittest import unittest
from unittest.mock import patch
from homeassistant.components.homekit.const import PROP_CELSIUS
from homeassistant.components.homekit.sensors import ( from homeassistant.components.homekit.sensors import (
TemperatureSensor, calc_temperature) TemperatureSensor, calc_temperature)
from homeassistant.const import ( from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_UNKNOWN) ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_UNKNOWN)
from tests.common import get_test_home_assistant 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(): def test_calc_temperature():
@ -27,19 +32,24 @@ class TestHomekitSensors(unittest.TestCase):
def setUp(self): def setUp(self):
"""Setup things to be run when tests are started.""" """Setup things to be run when tests are started."""
self.hass = get_test_home_assistant() self.hass = get_test_home_assistant()
get_patch_paths('sensors')
def tearDown(self): def tearDown(self):
"""Stop down everything that was started.""" """Stop down everything that was started."""
self.hass.stop() self.hass.stop()
def test_temperature_celsius(self): def test_temperature(self):
"""Test if accessory is updated after state change.""" """Test if accessory is updated after state change."""
temperature_sensor = 'sensor.temperature' temperature_sensor = 'sensor.temperature'
acc = TemperatureSensor(self.hass, temperature_sensor, 'Temperature') with patch(PATH_ACC, side_effect=mock_preload_service):
acc.run() 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.value, 0.0)
self.assertEqual(acc.char_temp.properties, PROP_CELSIUS)
self.hass.states.set(temperature_sensor, STATE_UNKNOWN, self.hass.states.set(temperature_sensor, STATE_UNKNOWN,
{ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS})

133
tests/mock/homekit.py Normal file
View file

@ -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 "<accessory \"{}\", services={}>".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 "<service \"{}\", chars={}, opt_chars={}>".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 "<char \"{}\", value={}>".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)