Skip to content

Commit

Permalink
Improved Homekit tests (#12800)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
cdce8p authored and balloob committed Mar 1, 2018
1 parent d338690 commit 168e1f0
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 93 deletions.
6 changes: 2 additions & 4 deletions homeassistant/components/homekit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
17 changes: 12 additions & 5 deletions homeassistant/components/homekit/accessories.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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)


Expand Down Expand Up @@ -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)
23 changes: 13 additions & 10 deletions homeassistant/components/homekit/const.py
Original file line number Diff line number Diff line change
@@ -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}
3 changes: 3 additions & 0 deletions homeassistant/components/homekit/covers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ def __init__(self, hass, entity_id, display_name):
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

Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/homekit/sensors.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def __init__(self, hass, entity_id, display_name):
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):
Expand Down
165 changes: 165 additions & 0 deletions tests/components/homekit/test_accessories.py
Original file line number Diff line number Diff line change
@@ -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')
16 changes: 10 additions & 6 deletions tests/components/homekit/test_covers.py
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -10,6 +11,9 @@
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):
Expand All @@ -35,22 +39,22 @@ def test_window_set_cover_position(self):
"""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})
self.hass.block_till_done()

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})
Expand Down
21 changes: 16 additions & 5 deletions tests/components/homekit/test_get_accessories.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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():
Expand All @@ -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
Loading

0 comments on commit 168e1f0

Please sign in to comment.