Cleanup homekit and remove aid storage from hass.data (#47488)

This commit is contained in:
J. Nick Koston 2021-03-11 20:05:03 -10:00 committed by GitHub
parent 33c4eb3434
commit f4b775b125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 147 additions and 205 deletions

View File

@ -34,7 +34,7 @@ from homeassistant.const import (
SERVICE_RELOAD,
)
from homeassistant.core import CoreState, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady, Unauthorized
from homeassistant.exceptions import Unauthorized
from homeassistant.helpers import device_registry, entity_registry
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entityfilter import BASE_FILTER_SCHEMA, FILTER_SCHEMA
@ -58,7 +58,6 @@ from . import ( # noqa: F401
from .accessories import HomeBridge, HomeDriver, get_accessory
from .aidmanager import AccessoryAidStorage
from .const import (
AID_STORAGE,
ATTR_INTERGRATION,
ATTR_MANUFACTURER,
ATTR_MODEL,
@ -241,9 +240,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
port = conf[CONF_PORT]
_LOGGER.debug("Begin setup HomeKit for %s", name)
aid_storage = AccessoryAidStorage(hass, entry.entry_id)
await aid_storage.async_initialize()
# ip_address and advertise_ip are yaml only
ip_address = conf.get(CONF_IP_ADDRESS)
advertise_ip = conf.get(CONF_ADVERTISE_IP)
@ -276,26 +272,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
entry.entry_id,
entry.title,
)
zeroconf_instance = await zeroconf.async_get_instance(hass)
# If the previous instance hasn't cleaned up yet
# we need to wait a bit
try:
await hass.async_add_executor_job(homekit.setup, zeroconf_instance)
except (OSError, AttributeError) as ex:
_LOGGER.warning(
"%s could not be setup because the local port %s is in use", name, port
)
raise ConfigEntryNotReady from ex
undo_listener = entry.add_update_listener(_async_update_listener)
hass.data[DOMAIN][entry.entry_id] = {
AID_STORAGE: aid_storage,
HOMEKIT: homekit,
UNDO_UPDATE_LISTENER: undo_listener,
UNDO_UPDATE_LISTENER: entry.add_update_listener(_async_update_listener),
}
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, homekit.async_stop)
if hass.state == CoreState.running:
await homekit.async_start()
elif auto_start:
@ -463,6 +447,7 @@ class HomeKit:
self._entry_id = entry_id
self._entry_title = entry_title
self._homekit_mode = homekit_mode
self.aid_storage = None
self.status = STATUS_READY
self.bridge = None
@ -470,7 +455,6 @@ class HomeKit:
def setup(self, zeroconf_instance):
"""Set up bridge and accessory driver."""
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.async_stop)
ip_addr = self._ip_address or get_local_ip()
persist_file = get_persist_fullpath_for_entry_id(self.hass, self._entry_id)
@ -503,10 +487,9 @@ class HomeKit:
self.driver.config_changed()
return
aid_storage = self.hass.data[DOMAIN][self._entry_id][AID_STORAGE]
removed = []
for entity_id in entity_ids:
aid = aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
aid = self.aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
if aid not in self.bridge.accessories:
continue
@ -531,9 +514,6 @@ class HomeKit:
def add_bridge_accessory(self, state):
"""Try adding accessory to bridge if configured beforehand."""
if not self._filter(state.entity_id):
return
# The bridge itself counts as an accessory
if len(self.bridge.accessories) + 1 >= MAX_DEVICES:
_LOGGER.warning(
@ -555,9 +535,7 @@ class HomeKit:
state.entity_id,
)
aid = self.hass.data[DOMAIN][self._entry_id][
AID_STORAGE
].get_or_allocate_aid_for_entity_id(state.entity_id)
aid = self.aid_storage.get_or_allocate_aid_for_entity_id(state.entity_id)
conf = self._config.pop(state.entity_id, {})
# If an accessory cannot be created or added due to an exception
# of any kind (usually in pyhap) it should not prevent
@ -578,15 +556,10 @@ class HomeKit:
acc = self.bridge.accessories.pop(aid)
return acc
async def async_start(self, *args):
"""Start the accessory driver."""
if self.status != STATUS_READY:
return
self.status = STATUS_WAIT
ent_reg = await entity_registry.async_get_registry(self.hass)
dev_reg = await device_registry.async_get_registry(self.hass)
async def async_configure_accessories(self):
"""Configure accessories for the included states."""
dev_reg = device_registry.async_get(self.hass)
ent_reg = entity_registry.async_get(self.hass)
device_lookup = ent_reg.async_get_device_class_lookup(
{
(BINARY_SENSOR_DOMAIN, DEVICE_CLASS_BATTERY_CHARGING),
@ -597,10 +570,9 @@ class HomeKit:
}
)
bridged_states = []
entity_states = []
for state in self.hass.states.async_all():
entity_id = state.entity_id
if not self._filter(entity_id):
continue
@ -611,17 +583,40 @@ class HomeKit:
)
self._async_configure_linked_sensors(ent_reg_ent, device_lookup, state)
bridged_states.append(state)
entity_states.append(state)
self._async_register_bridge(dev_reg)
await self._async_start(bridged_states)
return entity_states
async def async_start(self, *args):
"""Load storage and start."""
if self.status != STATUS_READY:
return
self.status = STATUS_WAIT
zc_instance = await zeroconf.async_get_instance(self.hass)
await self.hass.async_add_executor_job(self.setup, zc_instance)
self.aid_storage = AccessoryAidStorage(self.hass, self._entry_id)
await self.aid_storage.async_initialize()
await self._async_create_accessories()
self._async_register_bridge()
_LOGGER.debug("Driver start for %s", self._name)
await self.driver.async_start()
self.status = STATUS_RUNNING
if self.driver.state.paired:
return
show_setup_message(
self.hass,
self._entry_id,
accessory_friendly_name(self._entry_title, self.driver.accessory),
self.driver.state.pincode,
self.driver.accessory.xhm_uri(),
)
@callback
def _async_register_bridge(self, dev_reg):
def _async_register_bridge(self):
"""Register the bridge as a device so homekit_controller and exclude it from discovery."""
dev_reg = device_registry.async_get(self.hass)
formatted_mac = device_registry.format_mac(self.driver.state.mac)
# Connections and identifiers are both used here.
#
@ -645,8 +640,9 @@ class HomeKit:
identifiers={identifier},
connections={connection},
manufacturer=MANUFACTURER,
name=self._name,
model=f"Home Assistant HomeKit {hk_mode_name}",
name=accessory_friendly_name(self._entry_title, self.driver.accessory),
model=f"HomeKit {hk_mode_name}",
entry_type="service",
)
@callback
@ -663,14 +659,13 @@ class HomeKit:
for device_id in devices_to_purge:
dev_reg.async_remove_device(device_id)
async def _async_start(self, entity_states):
"""Start the accessory."""
async def _async_create_accessories(self):
"""Create the accessories."""
entity_states = await self.async_configure_accessories()
if self._homekit_mode == HOMEKIT_MODE_ACCESSORY:
state = entity_states[0]
conf = self._config.pop(state.entity_id, {})
acc = get_accessory(self.hass, self.driver, state, STANDALONE_AID, conf)
self.driver.add_accessory(acc)
else:
self.bridge = HomeBridge(self.hass, self.driver, self._name)
for state in entity_states:
@ -679,15 +674,6 @@ class HomeKit:
await self.hass.async_add_executor_job(self.driver.add_accessory, acc)
if not self.driver.state.paired:
show_setup_message(
self.hass,
self._entry_id,
accessory_friendly_name(self._entry_title, self.driver.accessory),
self.driver.state.pincode,
self.driver.accessory.xhm_uri(),
)
async def async_stop(self, *args):
"""Stop the accessory driver."""
if self.status != STATUS_RUNNING:

View File

@ -5,7 +5,6 @@ DEBOUNCE_TIMEOUT = 0.5
DEVICE_PRECISION_LEEWAY = 6
DOMAIN = "homekit"
HOMEKIT_FILE = ".homekit.state"
AID_STORAGE = "homekit-aid-allocations"
HOMEKIT_PAIRING_QR = "homekit-pairing-qr"
HOMEKIT_PAIRING_QR_SECRET = "homekit-pairing-qr-secret"
HOMEKIT = "homekit"

View File

@ -487,8 +487,10 @@ def accessory_friendly_name(hass_name, accessory):
see both to identify the accessory.
"""
accessory_mdns_name = accessory.display_name
if hass_name.startswith(accessory_mdns_name):
if hass_name.casefold().startswith(accessory_mdns_name.casefold()):
return hass_name
if accessory_mdns_name.casefold().startswith(hass_name.casefold()):
return accessory_mdns_name
return f"{hass_name} ({accessory_mdns_name})"

View File

@ -14,7 +14,9 @@ def hk_driver(loop):
"""Return a custom AccessoryDriver instance for HomeKit accessory init."""
with patch("pyhap.accessory_driver.Zeroconf"), patch(
"pyhap.accessory_driver.AccessoryEncoder"
), patch("pyhap.accessory_driver.HAPServer"), patch(
), patch("pyhap.accessory_driver.HAPServer.async_stop"), patch(
"pyhap.accessory_driver.HAPServer.async_start"
), patch(
"pyhap.accessory_driver.AccessoryDriver.publish"
), patch(
"pyhap.accessory_driver.AccessoryDriver.persist"

View File

@ -1,4 +1,5 @@
"""Tests for the HomeKit component."""
import asyncio
import os
from typing import Dict
from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch
@ -23,7 +24,6 @@ from homeassistant.components.homekit import (
)
from homeassistant.components.homekit.accessories import HomeBridge
from homeassistant.components.homekit.const import (
AID_STORAGE,
BRIDGE_NAME,
BRIDGE_SERIAL_NUMBER,
CONF_AUTO_START,
@ -47,7 +47,6 @@ from homeassistant.const import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
EVENT_HOMEASSISTANT_STARTED,
EVENT_HOMEASSISTANT_STOP,
PERCENTAGE,
SERVICE_RELOAD,
STATE_ON,
@ -98,8 +97,28 @@ def _mock_homekit(hass, entry, homekit_mode, entity_filter=None):
)
def _mock_homekit_bridge(hass, entry):
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = MagicMock()
return homekit
def _mock_accessories(accessory_count):
accessories = {}
for idx in range(accessory_count + 1):
accessories[idx + 1000] = MagicMock(async_stop=AsyncMock())
return accessories
def _mock_pyhap_bridge():
return MagicMock(
aid=1, accessories=_mock_accessories(10), display_name="HomeKit Bridge"
)
async def test_setup_min(hass, mock_zeroconf):
"""Test async_setup with min config options."""
await async_setup_component(hass, "persistent_notification", {})
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT},
@ -126,18 +145,16 @@ async def test_setup_min(hass, mock_zeroconf):
entry.entry_id,
entry.title,
)
assert mock_homekit().setup.called is True
# Test auto start enabled
mock_homekit.reset_mock()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
mock_homekit().async_start.assert_called()
assert mock_homekit().async_start.called is True
async def test_setup_auto_start_disabled(hass, mock_zeroconf):
"""Test async_setup with auto start disabled and test service calls."""
await async_setup_component(hass, "persistent_notification", {})
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_NAME: "Test Name", CONF_PORT: 11111, CONF_IP_ADDRESS: "172.0.0.0"},
@ -164,7 +181,6 @@ async def test_setup_auto_start_disabled(hass, mock_zeroconf):
entry.entry_id,
entry.title,
)
assert mock_homekit().setup.called is True
# Test auto_start disabled
homekit.reset_mock()
@ -237,9 +253,6 @@ async def test_homekit_setup(hass, hk_driver, mock_zeroconf):
)
assert homekit.driver.safe_mode is False
# Test if stop listener is setup
assert hass.bus.async_listeners().get(EVENT_HOMEASSISTANT_STOP) == 1
async def test_homekit_setup_ip_address(hass, hk_driver, mock_zeroconf):
"""Test setup with given IP address."""
@ -321,40 +334,37 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_zeroconf):
async def test_homekit_add_accessory(hass, mock_zeroconf):
"""Add accessory if config exists and get_acc returns an accessory."""
await async_setup_component(hass, "persistent_notification", {})
entry = MockConfigEntry(
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
)
entry.add_to_hass(hass)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = "driver"
homekit.bridge = mock_bridge = Mock()
homekit.bridge.accessories = range(10)
homekit.async_start = AsyncMock()
homekit = _mock_homekit_bridge(hass, entry)
mock_acc = Mock(category="any")
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
mock_acc = Mock(category="any")
homekit.bridge = _mock_pyhap_bridge()
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc:
mock_get_acc.side_effect = [None, mock_acc, None]
state = State("light.demo", "on")
homekit.add_bridge_accessory(state)
mock_get_acc.assert_called_with(hass, ANY, ANY, 1403373688, {})
assert not mock_bridge.add_accessory.called
assert not homekit.bridge.add_accessory.called
state = State("demo.test", "on")
homekit.add_bridge_accessory(state)
mock_get_acc.assert_called_with(hass, ANY, ANY, 600325356, {})
assert mock_bridge.add_accessory.called
assert homekit.bridge.add_accessory.called
state = State("demo.test_2", "on")
homekit.add_bridge_accessory(state)
mock_get_acc.assert_called_with(hass, ANY, ANY, 1467253281, {})
assert mock_bridge.add_accessory.called
assert homekit.bridge.add_accessory.called
@pytest.mark.parametrize("acc_category", [CATEGORY_TELEVISION, CATEGORY_CAMERA])
@ -362,29 +372,27 @@ async def test_homekit_warn_add_accessory_bridge(
hass, acc_category, mock_zeroconf, caplog
):
"""Test we warn when adding cameras or tvs to a bridge."""
await async_setup_component(hass, "persistent_notification", {})
entry = MockConfigEntry(
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
)
entry.add_to_hass(hass)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = "driver"
homekit.bridge = mock_bridge = Mock()
homekit.bridge.accessories = range(10)
homekit.async_start = AsyncMock()
homekit = _mock_homekit_bridge(hass, entry)
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit):
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
mock_camera_acc = Mock(category=acc_category)
homekit.bridge = _mock_pyhap_bridge()
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc:
mock_get_acc.side_effect = [None, mock_camera_acc, None]
state = State("camera.test", "on")
homekit.add_bridge_accessory(state)
mock_get_acc.assert_called_with(hass, ANY, ANY, 1508819236, {})
assert not mock_bridge.add_accessory.called
assert not homekit.bridge.add_accessory.called
assert "accessory mode" in caplog.text
@ -396,12 +404,12 @@ async def test_homekit_remove_accessory(hass, mock_zeroconf):
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = "driver"
homekit.bridge = mock_bridge = Mock()
mock_bridge.accessories = {"light.demo": "acc"}
homekit.bridge = _mock_pyhap_bridge()
homekit.bridge.accessories = {"light.demo": "acc"}
acc = homekit.remove_bridge_accessory("light.demo")
assert acc == "acc"
assert len(mock_bridge.accessories) == 0
assert len(homekit.bridge.accessories) == 0
async def test_homekit_entity_filter(hass, mock_zeroconf):
@ -413,20 +421,14 @@ async def test_homekit_entity_filter(hass, mock_zeroconf):
homekit.bridge = Mock()
homekit.bridge.accessories = {}
hass.states.async_set("cover.test", "open")
hass.states.async_set("demo.test", "on")
hass.states.async_set("light.demo", "on")
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc:
mock_get_acc.return_value = None
homekit.add_bridge_accessory(State("cover.test", "open"))
assert mock_get_acc.called is True
mock_get_acc.reset_mock()
homekit.add_bridge_accessory(State("demo.test", "on"))
assert mock_get_acc.called is True
mock_get_acc.reset_mock()
homekit.add_bridge_accessory(State("light.demo", "light"))
assert mock_get_acc.called is False
filtered_states = await homekit.async_configure_accessories()
assert hass.states.get("cover.test") in filtered_states
assert hass.states.get("demo.test") in filtered_states
assert hass.states.get("light.demo") not in filtered_states
async def test_homekit_entity_glob_filter(hass, mock_zeroconf):
@ -441,39 +443,29 @@ async def test_homekit_entity_glob_filter(hass, mock_zeroconf):
homekit.bridge = Mock()
homekit.bridge.accessories = {}
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc:
mock_get_acc.return_value = None
hass.states.async_set("cover.test", "open")
hass.states.async_set("demo.test", "on")
hass.states.async_set("cover.excluded_test", "open")
hass.states.async_set("light.included_test", "on")
homekit.add_bridge_accessory(State("cover.test", "open"))
assert mock_get_acc.called is True
mock_get_acc.reset_mock()
homekit.add_bridge_accessory(State("demo.test", "on"))
assert mock_get_acc.called is True
mock_get_acc.reset_mock()
homekit.add_bridge_accessory(State("cover.excluded_test", "open"))
assert mock_get_acc.called is False
mock_get_acc.reset_mock()
homekit.add_bridge_accessory(State("light.included_test", "light"))
assert mock_get_acc.called is True
mock_get_acc.reset_mock()
filtered_states = await homekit.async_configure_accessories()
assert hass.states.get("cover.test") in filtered_states
assert hass.states.get("demo.test") in filtered_states
assert hass.states.get("cover.excluded_test") not in filtered_states
assert hass.states.get("light.included_test") in filtered_states
async def test_homekit_start(hass, hk_driver, device_reg):
async def test_homekit_start(hass, hk_driver, mock_zeroconf, device_reg):
"""Test HomeKit start method."""
entry = await async_init_integration(hass)
pin = b"123-45-678"
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.bridge = Mock()
homekit.bridge.accessories = []
homekit.driver = hk_driver
# pylint: disable=protected-access
homekit._filter = Mock(return_value=True)
homekit.driver.accessory = Accessory(hk_driver, "any")
acc = Accessory(hk_driver, "any")
homekit.driver.accessory = acc
connection = (device_registry.CONNECTION_NETWORK_MAC, "AA:BB:CC:DD:EE:FF")
bridge_with_wrong_mac = device_reg.async_get_or_create(
@ -491,8 +483,6 @@ async def test_homekit_start(hass, hk_driver, device_reg):
with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch(
f"{PATH_HOMEKIT}.show_setup_message"
) as mock_setup_msg, patch(
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
) as hk_driver_add_acc, patch(
"pyhap.accessory_driver.AccessoryDriver.async_start"
) as hk_driver_start:
await homekit.async_start()
@ -500,9 +490,8 @@ async def test_homekit_start(hass, hk_driver, device_reg):
await hass.async_block_till_done()
mock_add_acc.assert_any_call(state)
mock_setup_msg.assert_called_with(
hass, entry.entry_id, "Mock Title (any)", pin, ANY
hass, entry.entry_id, "Mock Title (Home Assistant Bridge)", ANY, ANY
)
hk_driver_add_acc.assert_called_with(homekit.bridge)
assert hk_driver_start.called
assert homekit.status == STATUS_RUNNING
@ -526,8 +515,6 @@ async def test_homekit_start(hass, hk_driver, device_reg):
with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch(
f"{PATH_HOMEKIT}.show_setup_message"
) as mock_setup_msg, patch(
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
) as hk_driver_add_acc, patch(
"pyhap.accessory_driver.AccessoryDriver.async_start"
) as hk_driver_start:
await homekit.async_start()
@ -545,7 +532,6 @@ async def test_homekit_start(hass, hk_driver, device_reg):
async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroconf):
"""Test HomeKit start method."""
pin = b"123-45-678"
entry = MockConfigEntry(
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
)
@ -565,17 +551,14 @@ async def test_homekit_start_with_a_broken_accessory(hass, hk_driver, mock_zeroc
with patch(f"{PATH_HOMEKIT}.get_accessory", side_effect=Exception), patch(
f"{PATH_HOMEKIT}.show_setup_message"
) as mock_setup_msg, patch(
"pyhap.accessory_driver.AccessoryDriver.add_accessory",
) as hk_driver_add_acc, patch(
"pyhap.accessory_driver.AccessoryDriver.async_start"
) as hk_driver_start:
await homekit.async_start()
await hass.async_block_till_done()
mock_setup_msg.assert_called_with(
hass, entry.entry_id, "Mock Title (any)", pin, ANY
hass, entry.entry_id, "Mock Title (Home Assistant Bridge)", ANY, ANY
)
hk_driver_add_acc.assert_called_with(homekit.bridge)
assert hk_driver_start.called
assert homekit.status == STATUS_RUNNING
@ -616,27 +599,23 @@ async def test_homekit_stop(hass):
async def test_homekit_reset_accessories(hass, mock_zeroconf):
"""Test adding too many accessories to HomeKit."""
await async_setup_component(hass, "persistent_notification", {})
entry = MockConfigEntry(
domain=DOMAIN, data={CONF_NAME: "mock_name", CONF_PORT: 12345}
)
entity_id = "light.demo"
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.bridge = Mock()
homekit.bridge.accessories = {}
with patch(f"{PATH_HOMEKIT}.HomeKit", return_value=homekit), patch(
f"{PATH_HOMEKIT}.HomeKit.setup"
), patch("pyhap.accessory.Bridge.add_accessory") as mock_add_accessory, patch(
"pyhap.accessory.Bridge.add_accessory"
) as mock_add_accessory, patch(
"pyhap.accessory_driver.AccessoryDriver.config_changed"
) as hk_driver_config_changed, patch(
"pyhap.accessory_driver.AccessoryDriver.async_start"
):
await async_init_entry(hass, entry)
aid = hass.data[DOMAIN][entry.entry_id][
AID_STORAGE
].get_or_allocate_aid_for_entity_id(entity_id)
aid = homekit.aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
homekit.bridge.accessories = {aid: "acc"}
homekit.status = STATUS_RUNNING
@ -675,10 +654,8 @@ async def test_homekit_too_many_accessories(hass, hk_driver, caplog, mock_zeroco
hass.states.async_set("light.demo3", "on")
with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch(
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
), patch(f"{PATH_HOMEKIT}.show_setup_message"), patch(
f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge
):
f"{PATH_HOMEKIT}.show_setup_message"
), patch(f"{PATH_HOMEKIT}.HomeBridge", _mock_bridge):
await homekit.async_start()
await hass.async_block_till_done()
assert "would exceed" in caplog.text
@ -693,9 +670,7 @@ async def test_homekit_finds_linked_batteries(
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = hk_driver
# pylint: disable=protected-access
homekit._filter = Mock(return_value=True)
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
homekit.bridge = MagicMock()
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
@ -735,17 +710,15 @@ async def test_homekit_finds_linked_batteries(
)
hass.states.async_set(light.entity_id, STATE_ON)
with patch.object(homekit.bridge, "add_accessory"), patch(
f"{PATH_HOMEKIT}.show_setup_message"
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
"pyhap.accessory_driver.AccessoryDriver.async_start"
):
with patch(f"{PATH_HOMEKIT}.show_setup_message"), patch(
f"{PATH_HOMEKIT}.get_accessory"
) as mock_get_acc, patch("pyhap.accessory_driver.AccessoryDriver.async_start"):
await homekit.async_start()
await hass.async_block_till_done()
mock_get_acc.assert_called_with(
hass,
hk_driver,
ANY,
ANY,
ANY,
{
@ -766,8 +739,6 @@ async def test_homekit_async_get_integration_fails(
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = hk_driver
# pylint: disable=protected-access
homekit._filter = Mock(return_value=True)
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
config_entry = MockConfigEntry(domain="test", data={})
@ -817,7 +788,7 @@ async def test_homekit_async_get_integration_fails(
mock_get_acc.assert_called_with(
hass,
hk_driver,
ANY,
ANY,
ANY,
{
@ -832,6 +803,7 @@ async def test_homekit_async_get_integration_fails(
async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf):
"""Test async_setup with imported config."""
await async_setup_component(hass, "persistent_notification", {})
entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_IMPORT,
@ -861,7 +833,6 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf):
entry.entry_id,
entry.title,
)
assert mock_homekit().setup.called is True
# Test auto start enabled
mock_homekit.reset_mock()
@ -871,20 +842,6 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_zeroconf):
mock_homekit().async_start.assert_called()
async def test_raise_config_entry_not_ready(hass, mock_zeroconf):
"""Test async_setup when the port is not available."""
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_NAME: BRIDGE_NAME, CONF_PORT: DEFAULT_PORT},
options={},
)
entry.add_to_hass(hass)
with patch(f"{PATH_HOMEKIT}.HomeKit.setup", side_effect=OSError):
assert not await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_zeroconf):
"""Test HomeKit uses system zeroconf."""
entry = MockConfigEntry(
@ -917,13 +874,12 @@ async def test_homekit_ignored_missing_devices(
hass, hk_driver, device_reg, entity_reg, mock_zeroconf
):
"""Test HomeKit handles a device in the entity registry but missing from the device registry."""
await async_setup_component(hass, "persistent_notification", {})
entry = await async_init_integration(hass)
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = hk_driver
# pylint: disable=protected-access
homekit._filter = Mock(return_value=True)
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
homekit.bridge = _mock_pyhap_bridge()
config_entry = MockConfigEntry(domain="test", data={})
config_entry.add_to_hass(hass)
@ -952,25 +908,28 @@ async def test_homekit_ignored_missing_devices(
light = entity_reg.async_get_or_create(
"light", "powerwall", "demo", device_id=device_entry.id
)
before_removal = entity_reg.entities.copy()
# Delete the device to make sure we fallback
# to using the platform
device_reg.async_remove_device(device_entry.id)
# Wait for the entities to be removed
await asyncio.sleep(0)
await asyncio.sleep(0)
# Restore the registry
entity_reg.entities = before_removal
hass.states.async_set(light.entity_id, STATE_ON)
hass.states.async_set("light.two", STATE_ON)
with patch.object(homekit.bridge, "add_accessory"), patch(
f"{PATH_HOMEKIT}.show_setup_message"
), patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
"pyhap.accessory_driver.AccessoryDriver.async_start"
):
with patch(f"{PATH_HOMEKIT}.get_accessory") as mock_get_acc, patch(
f"{PATH_HOMEKIT}.HomeBridge", return_value=homekit.bridge
), patch("pyhap.accessory_driver.AccessoryDriver.async_start"):
await homekit.async_start()
await hass.async_block_till_done()
await hass.async_block_till_done()
mock_get_acc.assert_any_call(
hass,
hk_driver,
ANY,
ANY,
ANY,
{
@ -990,8 +949,6 @@ async def test_homekit_finds_linked_motion_sensors(
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = hk_driver
# pylint: disable=protected-access
homekit._filter = Mock(return_value=True)
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
config_entry = MockConfigEntry(domain="test", data={})
@ -1032,7 +989,7 @@ async def test_homekit_finds_linked_motion_sensors(
mock_get_acc.assert_called_with(
hass,
hk_driver,
ANY,
ANY,
ANY,
{
@ -1053,7 +1010,6 @@ async def test_homekit_finds_linked_humidity_sensors(
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE)
homekit.driver = hk_driver
homekit._filter = Mock(return_value=True)
homekit.bridge = HomeBridge(hass, hk_driver, "mock_bridge")
config_entry = MockConfigEntry(domain="test", data={})
@ -1097,7 +1053,7 @@ async def test_homekit_finds_linked_humidity_sensors(
mock_get_acc.assert_called_with(
hass,
hk_driver,
ANY,
ANY,
ANY,
{
@ -1111,6 +1067,7 @@ async def test_homekit_finds_linked_humidity_sensors(
async def test_reload(hass, mock_zeroconf):
"""Test we can reload from yaml."""
await async_setup_component(hass, "persistent_notification", {})
entry = MockConfigEntry(
domain=DOMAIN,
source=SOURCE_IMPORT,
@ -1121,7 +1078,6 @@ async def test_reload(hass, mock_zeroconf):
with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit:
mock_homekit.return_value = homekit = Mock()
type(homekit).async_start = AsyncMock()
assert await async_setup_component(
hass, "homekit", {"homekit": {CONF_NAME: "reloadable", CONF_PORT: 12345}}
)
@ -1140,7 +1096,6 @@ async def test_reload(hass, mock_zeroconf):
entry.entry_id,
entry.title,
)
assert mock_homekit().setup.called is True
yaml_path = os.path.join(
_get_fixtures_base_path(),
"fixtures",
@ -1156,7 +1111,6 @@ async def test_reload(hass, mock_zeroconf):
"pyhap.accessory_driver.AccessoryDriver.async_start"
):
mock_homekit2.return_value = homekit = Mock()
type(homekit).async_start = AsyncMock()
await hass.services.async_call(
"homekit",
SERVICE_RELOAD,
@ -1178,33 +1132,30 @@ async def test_reload(hass, mock_zeroconf):
entry.entry_id,
entry.title,
)
assert mock_homekit2().setup.called is True
def _get_fixtures_base_path():
return os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg):
async def test_homekit_start_in_accessory_mode(
hass, hk_driver, mock_zeroconf, device_reg
):
"""Test HomeKit start method in accessory mode."""
entry = await async_init_integration(hass)
pin = b"123-45-678"
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_ACCESSORY)
homekit.bridge = Mock()
homekit.bridge.accessories = []
homekit.driver = hk_driver
# pylint: disable=protected-access
homekit._filter = Mock(return_value=True)
homekit.driver.accessory = Accessory(hk_driver, "any")
hass.states.async_set("light.demo", "on")
with patch(f"{PATH_HOMEKIT}.HomeKit.add_bridge_accessory") as mock_add_acc, patch(
"pyhap.accessory_driver.AccessoryDriver.add_accessory"
), patch(f"{PATH_HOMEKIT}.show_setup_message") as mock_setup_msg, patch(
f"{PATH_HOMEKIT}.show_setup_message"
) as mock_setup_msg, patch(
"pyhap.accessory_driver.AccessoryDriver.async_start"
) as hk_driver_start:
await homekit.async_start()
@ -1212,7 +1163,7 @@ async def test_homekit_start_in_accessory_mode(hass, hk_driver, device_reg):
await hass.async_block_till_done()
mock_add_acc.assert_not_called()
mock_setup_msg.assert_called_with(
hass, entry.entry_id, "Mock Title (any)", pin, ANY
hass, entry.entry_id, "Mock Title (demo)", ANY, ANY
)
assert hk_driver_start.called
assert homekit.status == STATUS_RUNNING

View File

@ -294,5 +294,7 @@ async def test_accessory_friendly_name():
accessory = Mock()
accessory.display_name = "same"
assert accessory_friendly_name("same", accessory) == "same"
assert accessory_friendly_name("Same", accessory) == "Same"
assert accessory_friendly_name("hass title", accessory) == "hass title (same)"
accessory.display_name = "Hass title 123"
assert accessory_friendly_name("hass title", accessory) == "Hass title 123"