2018-03-01 23:20:02 +00:00
|
|
|
"""Test all functions related to the basic accessory implementation.
|
|
|
|
|
|
|
|
This includes tests for all mock object types.
|
|
|
|
"""
|
2018-04-06 21:11:53 +00:00
|
|
|
from datetime import datetime, timedelta
|
2018-05-10 23:21:59 +00:00
|
|
|
from unittest.mock import patch, Mock
|
2018-03-01 23:20:02 +00:00
|
|
|
|
2018-05-21 02:25:53 +00:00
|
|
|
import pytest
|
|
|
|
|
2018-03-01 23:20:02 +00:00
|
|
|
from homeassistant.components.homekit.accessories import (
|
2019-07-31 19:25:30 +00:00
|
|
|
debounce,
|
|
|
|
HomeAccessory,
|
|
|
|
HomeBridge,
|
|
|
|
HomeDriver,
|
|
|
|
)
|
2018-03-01 23:20:02 +00:00
|
|
|
from homeassistant.components.homekit.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_DISPLAY_NAME,
|
|
|
|
ATTR_VALUE,
|
|
|
|
BRIDGE_MODEL,
|
|
|
|
BRIDGE_NAME,
|
|
|
|
BRIDGE_SERIAL_NUMBER,
|
|
|
|
CHAR_FIRMWARE_REVISION,
|
|
|
|
CHAR_MANUFACTURER,
|
|
|
|
CHAR_MODEL,
|
|
|
|
CHAR_NAME,
|
|
|
|
CHAR_SERIAL_NUMBER,
|
|
|
|
CONF_LINKED_BATTERY_SENSOR,
|
|
|
|
CONF_LOW_BATTERY_THRESHOLD,
|
|
|
|
MANUFACTURER,
|
|
|
|
SERV_ACCESSORY_INFO,
|
|
|
|
)
|
2018-06-17 18:54:34 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
__version__,
|
|
|
|
ATTR_BATTERY_CHARGING,
|
|
|
|
ATTR_BATTERY_LEVEL,
|
|
|
|
ATTR_ENTITY_ID,
|
|
|
|
ATTR_SERVICE,
|
|
|
|
ATTR_NOW,
|
|
|
|
EVENT_TIME_CHANGED,
|
|
|
|
)
|
2018-04-06 21:11:53 +00:00
|
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
|
2018-10-16 11:32:53 +00:00
|
|
|
from tests.common import async_mock_service
|
|
|
|
|
2018-04-06 21:11:53 +00:00
|
|
|
|
2018-05-10 23:21:59 +00:00
|
|
|
async def test_debounce(hass):
|
|
|
|
"""Test add_timeout decorator function."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2018-05-10 23:21:59 +00:00
|
|
|
def demo_func(*args):
|
|
|
|
nonlocal arguments, counter
|
|
|
|
counter += 1
|
|
|
|
arguments = args
|
|
|
|
|
|
|
|
arguments = None
|
|
|
|
counter = 0
|
2018-05-30 10:39:27 +00:00
|
|
|
mock = Mock(hass=hass, debounce={})
|
2018-05-10 23:21:59 +00:00
|
|
|
|
|
|
|
debounce_demo = debounce(demo_func)
|
2019-07-31 19:25:30 +00:00
|
|
|
assert debounce_demo.__name__ == "demo_func"
|
2018-05-10 23:21:59 +00:00
|
|
|
now = datetime(2018, 1, 1, 20, 0, 0, tzinfo=dt_util.UTC)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
|
|
|
await hass.async_add_job(debounce_demo, mock, "value")
|
|
|
|
hass.bus.async_fire(EVENT_TIME_CHANGED, {ATTR_NOW: now + timedelta(seconds=3)})
|
2018-05-10 23:21:59 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert counter == 1
|
|
|
|
assert len(arguments) == 2
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
|
|
|
await hass.async_add_job(debounce_demo, mock, "value")
|
|
|
|
await hass.async_add_job(debounce_demo, mock, "value")
|
2018-05-10 23:21:59 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.bus.async_fire(EVENT_TIME_CHANGED, {ATTR_NOW: now + timedelta(seconds=3)})
|
2018-05-10 23:21:59 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert counter == 2
|
|
|
|
|
|
|
|
|
2018-05-29 20:43:26 +00:00
|
|
|
async def test_home_accessory(hass, hk_driver):
|
2018-05-10 23:21:59 +00:00
|
|
|
"""Test HomeAccessory class."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entity_id = "homekit.accessory"
|
2018-05-12 15:10:19 +00:00
|
|
|
hass.states.async_set(entity_id, None)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
acc = HomeAccessory(hass, hk_driver, "Home Accessory", entity_id, 2, None)
|
2018-05-10 23:21:59 +00:00
|
|
|
assert acc.hass == hass
|
2019-07-31 19:25:30 +00:00
|
|
|
assert acc.display_name == "Home Accessory"
|
2018-05-11 12:22:45 +00:00
|
|
|
assert acc.aid == 2
|
2018-05-10 23:21:59 +00:00
|
|
|
assert acc.category == 1 # Category.OTHER
|
|
|
|
assert len(acc.services) == 1
|
|
|
|
serv = acc.services[0] # SERV_ACCESSORY_INFO
|
|
|
|
assert serv.display_name == SERV_ACCESSORY_INFO
|
2019-07-31 19:25:30 +00:00
|
|
|
assert serv.get_characteristic(CHAR_NAME).value == "Home Accessory"
|
2018-05-10 23:21:59 +00:00
|
|
|
assert serv.get_characteristic(CHAR_MANUFACTURER).value == MANUFACTURER
|
2019-07-31 19:25:30 +00:00
|
|
|
assert serv.get_characteristic(CHAR_MODEL).value == "Homekit"
|
|
|
|
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == "homekit.accessory"
|
2018-05-10 23:21:59 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.states.async_set(entity_id, "on")
|
2018-05-10 23:21:59 +00:00
|
|
|
await hass.async_block_till_done()
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch(
|
|
|
|
"homeassistant.components.homekit.accessories." "HomeAccessory.update_state"
|
|
|
|
) as mock_update_state:
|
2018-05-21 02:25:53 +00:00
|
|
|
await hass.async_add_job(acc.run)
|
2018-05-30 10:39:27 +00:00
|
|
|
await hass.async_block_till_done()
|
2018-05-21 02:25:53 +00:00
|
|
|
state = hass.states.get(entity_id)
|
|
|
|
mock_update_state.assert_called_with(state)
|
2018-05-10 23:21:59 +00:00
|
|
|
|
2018-05-21 02:25:53 +00:00
|
|
|
hass.states.async_remove(entity_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_update_state.call_count == 1
|
2018-05-12 15:10:19 +00:00
|
|
|
|
2018-05-21 02:25:53 +00:00
|
|
|
with pytest.raises(NotImplementedError):
|
2019-07-31 19:25:30 +00:00
|
|
|
acc.update_state("new_state")
|
2018-05-21 02:25:53 +00:00
|
|
|
|
|
|
|
# Test model name from domain
|
2019-07-31 19:25:30 +00:00
|
|
|
entity_id = "test_model.demo"
|
2018-06-17 18:54:34 +00:00
|
|
|
hass.states.async_set(entity_id, None)
|
|
|
|
await hass.async_block_till_done()
|
2019-07-31 19:25:30 +00:00
|
|
|
acc = HomeAccessory(hass, hk_driver, "test_name", entity_id, 2, None)
|
2018-05-10 23:21:59 +00:00
|
|
|
serv = acc.services[0] # SERV_ACCESSORY_INFO
|
2019-07-31 19:25:30 +00:00
|
|
|
assert serv.get_characteristic(CHAR_MODEL).value == "Test Model"
|
2018-05-10 23:21:59 +00:00
|
|
|
|
|
|
|
|
2019-02-17 07:04:29 +00:00
|
|
|
async def test_battery_service(hass, hk_driver, caplog):
|
2018-06-17 18:54:34 +00:00
|
|
|
"""Test battery service."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entity_id = "homekit.accessory"
|
2018-06-17 18:54:34 +00:00
|
|
|
hass.states.async_set(entity_id, None, {ATTR_BATTERY_LEVEL: 50})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
acc = HomeAccessory(hass, hk_driver, "Battery Service", entity_id, 2, None)
|
2018-08-07 07:26:58 +00:00
|
|
|
acc.update_state = lambda x: None
|
2018-06-17 18:54:34 +00:00
|
|
|
assert acc._char_battery.value == 0
|
|
|
|
assert acc._char_low_battery.value == 0
|
|
|
|
assert acc._char_charging.value == 2
|
|
|
|
|
|
|
|
await hass.async_add_job(acc.run)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 50
|
|
|
|
assert acc._char_low_battery.value == 0
|
|
|
|
assert acc._char_charging.value == 2
|
|
|
|
|
|
|
|
hass.states.async_set(entity_id, None, {ATTR_BATTERY_LEVEL: 15})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 15
|
|
|
|
assert acc._char_low_battery.value == 1
|
|
|
|
assert acc._char_charging.value == 2
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.states.async_set(entity_id, None, {ATTR_BATTERY_LEVEL: "error"})
|
2019-02-17 07:04:29 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 15
|
|
|
|
assert acc._char_low_battery.value == 1
|
|
|
|
assert acc._char_charging.value == 2
|
2019-07-31 19:25:30 +00:00
|
|
|
assert "ERROR" not in caplog.text
|
2019-02-17 07:04:29 +00:00
|
|
|
|
2018-06-17 18:54:34 +00:00
|
|
|
# Test charging
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.states.async_set(
|
|
|
|
entity_id, None, {ATTR_BATTERY_LEVEL: 10, ATTR_BATTERY_CHARGING: True}
|
|
|
|
)
|
2018-06-17 18:54:34 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
acc = HomeAccessory(hass, hk_driver, "Battery Service", entity_id, 2, None)
|
2018-08-07 07:26:58 +00:00
|
|
|
acc.update_state = lambda x: None
|
2018-06-17 18:54:34 +00:00
|
|
|
assert acc._char_battery.value == 0
|
|
|
|
assert acc._char_low_battery.value == 0
|
|
|
|
assert acc._char_charging.value == 2
|
|
|
|
|
|
|
|
await hass.async_add_job(acc.run)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 10
|
|
|
|
assert acc._char_low_battery.value == 1
|
|
|
|
assert acc._char_charging.value == 1
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.states.async_set(
|
|
|
|
entity_id, None, {ATTR_BATTERY_LEVEL: 100, ATTR_BATTERY_CHARGING: False}
|
|
|
|
)
|
2018-06-17 18:54:34 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 100
|
|
|
|
assert acc._char_low_battery.value == 0
|
|
|
|
assert acc._char_charging.value == 0
|
|
|
|
|
|
|
|
|
2019-04-09 21:13:48 +00:00
|
|
|
async def test_linked_battery_sensor(hass, hk_driver, caplog):
|
|
|
|
"""Test battery service with linked_battery_sensor."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entity_id = "homekit.accessory"
|
|
|
|
linked_battery = "sensor.battery"
|
|
|
|
hass.states.async_set(entity_id, "open", {ATTR_BATTERY_LEVEL: 100})
|
2019-04-09 21:13:48 +00:00
|
|
|
hass.states.async_set(linked_battery, 50, None)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
acc = HomeAccessory(
|
|
|
|
hass,
|
|
|
|
hk_driver,
|
|
|
|
"Battery Service",
|
|
|
|
entity_id,
|
|
|
|
2,
|
|
|
|
{CONF_LINKED_BATTERY_SENSOR: linked_battery},
|
|
|
|
)
|
2019-04-09 21:13:48 +00:00
|
|
|
acc.update_state = lambda x: None
|
|
|
|
assert acc.linked_battery_sensor == linked_battery
|
|
|
|
|
|
|
|
await hass.async_add_job(acc.run)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 50
|
|
|
|
assert acc._char_low_battery.value == 0
|
|
|
|
assert acc._char_charging.value == 2
|
|
|
|
|
|
|
|
hass.states.async_set(linked_battery, 10, None)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 10
|
|
|
|
assert acc._char_low_battery.value == 1
|
|
|
|
|
|
|
|
# Ignore battery change on entity if it has linked_battery
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.states.async_set(entity_id, "open", {ATTR_BATTERY_LEVEL: 90})
|
2019-04-09 21:13:48 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 10
|
|
|
|
|
|
|
|
# Test none numeric state for linked_battery
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.states.async_set(linked_battery, "error", None)
|
2019-04-09 21:13:48 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 10
|
2019-07-31 19:25:30 +00:00
|
|
|
assert "ERROR" not in caplog.text
|
2019-04-09 21:13:48 +00:00
|
|
|
|
2019-04-30 00:02:54 +00:00
|
|
|
# Test charging & low battery threshold
|
2019-04-09 21:13:48 +00:00
|
|
|
hass.states.async_set(linked_battery, 20, {ATTR_BATTERY_CHARGING: True})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
acc = HomeAccessory(
|
|
|
|
hass,
|
|
|
|
hk_driver,
|
|
|
|
"Battery Service",
|
|
|
|
entity_id,
|
|
|
|
2,
|
|
|
|
{CONF_LINKED_BATTERY_SENSOR: linked_battery, CONF_LOW_BATTERY_THRESHOLD: 50},
|
|
|
|
)
|
2019-04-09 21:13:48 +00:00
|
|
|
acc.update_state = lambda x: None
|
|
|
|
await hass.async_add_job(acc.run)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 20
|
2019-04-30 00:02:54 +00:00
|
|
|
assert acc._char_low_battery.value == 1
|
2019-04-09 21:13:48 +00:00
|
|
|
assert acc._char_charging.value == 1
|
|
|
|
|
|
|
|
hass.states.async_set(linked_battery, 100, {ATTR_BATTERY_CHARGING: False})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert acc._char_battery.value == 100
|
|
|
|
assert acc._char_low_battery.value == 0
|
|
|
|
assert acc._char_charging.value == 0
|
|
|
|
|
|
|
|
|
2018-10-16 11:32:53 +00:00
|
|
|
async def test_call_service(hass, hk_driver, events):
|
|
|
|
"""Test call_service method."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entity_id = "homekit.accessory"
|
2018-10-16 11:32:53 +00:00
|
|
|
hass.states.async_set(entity_id, None)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
acc = HomeAccessory(hass, hk_driver, "Home Accessory", entity_id, 2, None)
|
|
|
|
call_service = async_mock_service(hass, "cover", "open_cover")
|
2018-10-16 11:32:53 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
test_domain = "cover"
|
|
|
|
test_service = "open_cover"
|
|
|
|
test_value = "value"
|
2018-10-16 11:32:53 +00:00
|
|
|
|
|
|
|
await acc.async_call_service(
|
2019-07-31 19:25:30 +00:00
|
|
|
test_domain, test_service, {ATTR_ENTITY_ID: entity_id}, test_value
|
|
|
|
)
|
2018-10-16 11:32:53 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(events) == 1
|
|
|
|
assert events[0].data == {
|
|
|
|
ATTR_ENTITY_ID: acc.entity_id,
|
|
|
|
ATTR_DISPLAY_NAME: acc.display_name,
|
|
|
|
ATTR_SERVICE: test_service,
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_VALUE: test_value,
|
2018-10-16 11:32:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
assert len(call_service) == 1
|
|
|
|
assert call_service[0].domain == test_domain
|
|
|
|
assert call_service[0].service == test_service
|
|
|
|
assert call_service[0].data == {ATTR_ENTITY_ID: entity_id}
|
|
|
|
|
|
|
|
|
2018-05-29 20:43:26 +00:00
|
|
|
def test_home_bridge(hk_driver):
|
2018-05-10 23:21:59 +00:00
|
|
|
"""Test HomeBridge class."""
|
2019-07-31 19:25:30 +00:00
|
|
|
bridge = HomeBridge("hass", hk_driver, BRIDGE_NAME)
|
|
|
|
assert bridge.hass == "hass"
|
2018-05-10 23:21:59 +00:00
|
|
|
assert bridge.display_name == BRIDGE_NAME
|
|
|
|
assert bridge.category == 2 # Category.BRIDGE
|
|
|
|
assert len(bridge.services) == 1
|
|
|
|
serv = bridge.services[0] # SERV_ACCESSORY_INFO
|
|
|
|
assert serv.display_name == SERV_ACCESSORY_INFO
|
|
|
|
assert serv.get_characteristic(CHAR_NAME).value == BRIDGE_NAME
|
|
|
|
assert serv.get_characteristic(CHAR_FIRMWARE_REVISION).value == __version__
|
|
|
|
assert serv.get_characteristic(CHAR_MANUFACTURER).value == MANUFACTURER
|
|
|
|
assert serv.get_characteristic(CHAR_MODEL).value == BRIDGE_MODEL
|
2019-07-31 19:25:30 +00:00
|
|
|
assert serv.get_characteristic(CHAR_SERIAL_NUMBER).value == BRIDGE_SERIAL_NUMBER
|
2018-05-10 23:21:59 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
bridge = HomeBridge("hass", hk_driver, "test_name")
|
|
|
|
assert bridge.display_name == "test_name"
|
2018-05-10 23:21:59 +00:00
|
|
|
assert len(bridge.services) == 1
|
|
|
|
serv = bridge.services[0] # SERV_ACCESSORY_INFO
|
|
|
|
|
|
|
|
# setup_message
|
|
|
|
bridge.setup_message()
|
|
|
|
|
|
|
|
|
|
|
|
def test_home_driver():
|
|
|
|
"""Test HomeDriver class."""
|
2019-07-31 19:25:30 +00:00
|
|
|
ip_address = "127.0.0.1"
|
2018-05-10 23:21:59 +00:00
|
|
|
port = 51826
|
2019-07-31 19:25:30 +00:00
|
|
|
path = ".homekit.state"
|
|
|
|
pin = b"123-45-678"
|
2018-05-10 23:21:59 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch("pyhap.accessory_driver.AccessoryDriver.__init__") as mock_driver:
|
|
|
|
driver = HomeDriver("hass", address=ip_address, port=port, persist_file=path)
|
2018-05-10 23:21:59 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_driver.assert_called_with(address=ip_address, port=port, persist_file=path)
|
2018-05-18 14:32:57 +00:00
|
|
|
driver.state = Mock(pincode=pin)
|
|
|
|
|
|
|
|
# pair
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch("pyhap.accessory_driver.AccessoryDriver.pair") as mock_pair, patch(
|
|
|
|
"homeassistant.components.homekit.accessories." "dismiss_setup_message"
|
|
|
|
) as mock_dissmiss_msg:
|
|
|
|
driver.pair("client_uuid", "client_public")
|
2018-05-18 14:32:57 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_pair.assert_called_with("client_uuid", "client_public")
|
|
|
|
mock_dissmiss_msg.assert_called_with("hass")
|
2018-05-18 14:32:57 +00:00
|
|
|
|
|
|
|
# unpair
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch("pyhap.accessory_driver.AccessoryDriver.unpair") as mock_unpair, patch(
|
|
|
|
"homeassistant.components.homekit.accessories." "show_setup_message"
|
|
|
|
) as mock_show_msg:
|
|
|
|
driver.unpair("client_uuid")
|
|
|
|
|
|
|
|
mock_unpair.assert_called_with("client_uuid")
|
|
|
|
mock_show_msg.assert_called_with("hass", pin)
|