Deprecate IPv6 zeroconf setting in favor of the network integration (#51173)

pull/52311/head^2
J. Nick Koston 2021-06-29 17:13:31 -10:00 committed by GitHub
parent 3c20f2dd42
commit 9f16e390f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 195 additions and 177 deletions

View File

@ -15,10 +15,9 @@ from zeroconf import (
InterfaceChoice, InterfaceChoice,
IPVersion, IPVersion,
NonUniqueNameException, NonUniqueNameException,
ServiceInfo,
ServiceStateChange, ServiceStateChange,
Zeroconf,
) )
from zeroconf.asyncio import AsyncServiceInfo
from homeassistant import config_entries, util from homeassistant import config_entries, util
from homeassistant.components import network from homeassistant.components import network
@ -35,7 +34,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.network import NoURLAvailableError, get_url
from homeassistant.loader import async_get_homekit, async_get_zeroconf, bind_hass from homeassistant.loader import async_get_homekit, async_get_zeroconf, bind_hass
from .models import HaAsyncZeroconf, HaServiceBrowser, HaZeroconf from .models import HaAsyncServiceBrowser, HaAsyncZeroconf, HaZeroconf
from .usage import install_multiple_zeroconf_catcher from .usage import install_multiple_zeroconf_catcher
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -70,6 +69,7 @@ CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.All( DOMAIN: vol.All(
cv.deprecated(CONF_DEFAULT_INTERFACE), cv.deprecated(CONF_DEFAULT_INTERFACE),
cv.deprecated(CONF_IPV6),
vol.Schema( vol.Schema(
{ {
vol.Optional(CONF_DEFAULT_INTERFACE): cv.boolean, vol.Optional(CONF_DEFAULT_INTERFACE): cv.boolean,
@ -119,16 +119,16 @@ async def _async_get_instance(hass: HomeAssistant, **zcargs: Any) -> HaAsyncZero
logging.getLogger("zeroconf").setLevel(logging.NOTSET) logging.getLogger("zeroconf").setLevel(logging.NOTSET)
aio_zc = HaAsyncZeroconf(**zcargs) zeroconf = HaZeroconf(**zcargs)
zeroconf = cast(HaZeroconf, aio_zc.zeroconf) aio_zc = HaAsyncZeroconf(zc=zeroconf)
install_multiple_zeroconf_catcher(zeroconf) install_multiple_zeroconf_catcher(zeroconf)
def _stop_zeroconf(_event: Event) -> None: async def _async_stop_zeroconf(_event: Event) -> None:
"""Stop Zeroconf.""" """Stop Zeroconf."""
zeroconf.ha_close() await aio_zc.ha_async_close()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_zeroconf) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_zeroconf)
hass.data[DOMAIN] = aio_zc hass.data[DOMAIN] = aio_zc
return aio_zc return aio_zc
@ -143,7 +143,6 @@ def _async_use_default_interface(adapters: list[Adapter]) -> bool:
async def async_setup(hass: HomeAssistant, config: dict) -> bool: async def async_setup(hass: HomeAssistant, config: dict) -> bool:
"""Set up Zeroconf and make Home Assistant discoverable.""" """Set up Zeroconf and make Home Assistant discoverable."""
zc_config = config.get(DOMAIN, {})
zc_args: dict = {} zc_args: dict = {}
adapters = await network.async_get_adapters(hass) adapters = await network.async_get_adapters(hass)
@ -158,16 +157,18 @@ async def async_setup(hass: HomeAssistant, config: dict) -> bool:
interfaces.append(ipv4s[0]["address"]) interfaces.append(ipv4s[0]["address"])
elif ipv6s := adapter["ipv6"]: elif ipv6s := adapter["ipv6"]:
interfaces.append(ipv6s[0]["scope_id"]) interfaces.append(ipv6s[0]["scope_id"])
if not zc_config.get(CONF_IPV6, DEFAULT_IPV6):
ipv6 = True
if not any(adapter["enabled"] and adapter["ipv6"] for adapter in adapters):
ipv6 = False
zc_args["ip_version"] = IPVersion.V4Only zc_args["ip_version"] = IPVersion.V4Only
aio_zc = await _async_get_instance(hass, **zc_args) aio_zc = await _async_get_instance(hass, **zc_args)
zeroconf = aio_zc.zeroconf zeroconf = cast(HaZeroconf, aio_zc.zeroconf)
zeroconf_types, homekit_models = await asyncio.gather( zeroconf_types, homekit_models = await asyncio.gather(
async_get_zeroconf(hass), async_get_homekit(hass) async_get_zeroconf(hass), async_get_homekit(hass)
) )
discovery = ZeroconfDiscovery(hass, zeroconf, zeroconf_types, homekit_models) discovery = ZeroconfDiscovery(hass, zeroconf, zeroconf_types, homekit_models, ipv6)
await discovery.async_setup() await discovery.async_setup()
async def _async_zeroconf_hass_start(_event: Event) -> None: async def _async_zeroconf_hass_start(_event: Event) -> None:
@ -230,7 +231,7 @@ async def _async_register_hass_zc_service(
_suppress_invalid_properties(params) _suppress_invalid_properties(params)
info = ServiceInfo( info = AsyncServiceInfo(
ZEROCONF_TYPE, ZEROCONF_TYPE,
name=f"{valid_location_name}.{ZEROCONF_TYPE}", name=f"{valid_location_name}.{ZEROCONF_TYPE}",
server=f"{uuid}.local.", server=f"{uuid}.local.",
@ -268,10 +269,10 @@ class FlowDispatcher:
self.hass.async_create_task(self._init_flow(flow)) self.hass.async_create_task(self._init_flow(flow))
self.pending_flows = [] self.pending_flows = []
def create(self, flow: ZeroconfFlow) -> None: def async_create(self, flow: ZeroconfFlow) -> None:
"""Create and add or queue a flow.""" """Create and add or queue a flow."""
if self.started: if self.started:
self.hass.create_task(self._init_flow(flow)) self.hass.async_create_task(self._init_flow(flow))
else: else:
self.pending_flows.append(flow) self.pending_flows.append(flow)
@ -288,18 +289,20 @@ class ZeroconfDiscovery:
def __init__( def __init__(
self, self,
hass: HomeAssistant, hass: HomeAssistant,
zeroconf: Zeroconf, zeroconf: HaZeroconf,
zeroconf_types: dict[str, list[dict[str, str]]], zeroconf_types: dict[str, list[dict[str, str]]],
homekit_models: dict[str, str], homekit_models: dict[str, str],
ipv6: bool,
) -> None: ) -> None:
"""Init discovery.""" """Init discovery."""
self.hass = hass self.hass = hass
self.zeroconf = zeroconf self.zeroconf = zeroconf
self.zeroconf_types = zeroconf_types self.zeroconf_types = zeroconf_types
self.homekit_models = homekit_models self.homekit_models = homekit_models
self.ipv6 = ipv6
self.flow_dispatcher: FlowDispatcher | None = None self.flow_dispatcher: FlowDispatcher | None = None
self.service_browser: HaServiceBrowser | None = None self.async_service_browser: HaAsyncServiceBrowser | None = None
async def async_setup(self) -> None: async def async_setup(self) -> None:
"""Start discovery.""" """Start discovery."""
@ -311,15 +314,15 @@ class ZeroconfDiscovery:
for hk_type in (ZEROCONF_TYPE, *HOMEKIT_TYPES): for hk_type in (ZEROCONF_TYPE, *HOMEKIT_TYPES):
if hk_type not in self.zeroconf_types: if hk_type not in self.zeroconf_types:
types.append(hk_type) types.append(hk_type)
_LOGGER.debug("Starting Zeroconf browser") _LOGGER.debug("Starting Zeroconf browser for: %s", types)
self.service_browser = HaServiceBrowser( self.async_service_browser = HaAsyncServiceBrowser(
self.zeroconf, types, handlers=[self.service_update] self.ipv6, self.zeroconf, types, handlers=[self.async_service_update]
) )
async def async_stop(self) -> None: async def async_stop(self) -> None:
"""Cancel the service browser and stop processing the queue.""" """Cancel the service browser and stop processing the queue."""
if self.service_browser: if self.async_service_browser:
await self.hass.async_add_executor_job(self.service_browser.cancel) await self.async_service_browser.async_cancel()
@callback @callback
def async_start(self) -> None: def async_start(self) -> None:
@ -327,21 +330,35 @@ class ZeroconfDiscovery:
assert self.flow_dispatcher is not None assert self.flow_dispatcher is not None
self.flow_dispatcher.async_start() self.flow_dispatcher.async_start()
def service_update( @callback
def async_service_update(
self, self,
zeroconf: Zeroconf, zeroconf: HaZeroconf,
service_type: str, service_type: str,
name: str, name: str,
state_change: ServiceStateChange, state_change: ServiceStateChange,
) -> None: ) -> None:
"""Service state changed.""" """Service state changed."""
_LOGGER.debug(
"service_update: type=%s name=%s state_change=%s",
service_type,
name,
state_change,
)
if state_change == ServiceStateChange.Removed: if state_change == ServiceStateChange.Removed:
return return
service_info = ServiceInfo(service_type, name) asyncio.create_task(self._process_service_update(zeroconf, service_type, name))
service_info.load_from_cache(zeroconf)
info = info_from_service(service_info) async def _process_service_update(
self, zeroconf: HaZeroconf, service_type: str, name: str
) -> None:
"""Process a zeroconf update."""
async_service_info = AsyncServiceInfo(service_type, name)
await async_service_info.async_request(zeroconf, 3000)
info = info_from_service(async_service_info)
if not info: if not info:
# Prevent the browser thread from collapsing # Prevent the browser thread from collapsing
_LOGGER.debug("Failed to get addresses for device %s", name) _LOGGER.debug("Failed to get addresses for device %s", name)
@ -353,7 +370,7 @@ class ZeroconfDiscovery:
# If we can handle it as a HomeKit discovery, we do that here. # If we can handle it as a HomeKit discovery, we do that here.
if service_type in HOMEKIT_TYPES: if service_type in HOMEKIT_TYPES:
if pending_flow := handle_homekit(self.hass, self.homekit_models, info): if pending_flow := handle_homekit(self.hass, self.homekit_models, info):
self.flow_dispatcher.create(pending_flow) self.flow_dispatcher.async_create(pending_flow)
# Continue on here as homekit_controller # Continue on here as homekit_controller
# still needs to get updates on devices # still needs to get updates on devices
# so it can see when the 'c#' field is updated. # so it can see when the 'c#' field is updated.
@ -415,7 +432,7 @@ class ZeroconfDiscovery:
"context": {"source": config_entries.SOURCE_ZEROCONF}, "context": {"source": config_entries.SOURCE_ZEROCONF},
"data": info, "data": info,
} }
self.flow_dispatcher.create(flow) self.flow_dispatcher.async_create(flow)
def handle_homekit( def handle_homekit(
@ -453,7 +470,7 @@ def handle_homekit(
return None return None
def info_from_service(service: ServiceInfo) -> HaServiceInfo | None: def info_from_service(service: AsyncServiceInfo) -> HaServiceInfo | None:
"""Return prepared info from mDNS entries.""" """Return prepared info from mDNS entries."""
properties: dict[str, Any] = {"_raw": {}} properties: dict[str, Any] = {"_raw": {}}

View File

@ -2,7 +2,7 @@
"domain": "zeroconf", "domain": "zeroconf",
"name": "Zero-configuration networking (zeroconf)", "name": "Zero-configuration networking (zeroconf)",
"documentation": "https://www.home-assistant.io/integrations/zeroconf", "documentation": "https://www.home-assistant.io/integrations/zeroconf",
"requirements": ["zeroconf==0.31.0"], "requirements": ["zeroconf==0.32.0"],
"dependencies": ["network", "api"], "dependencies": ["network", "api"],
"codeowners": ["@bdraco"], "codeowners": ["@bdraco"],
"quality_scale": "internal", "quality_scale": "internal",

View File

@ -1,10 +1,11 @@
"""Models for Zeroconf.""" """Models for Zeroconf."""
import asyncio
from typing import Any from typing import Any
from zeroconf import DNSPointer, DNSRecord, ServiceBrowser, Zeroconf from zeroconf import DNSAddress, DNSRecord, Zeroconf
from zeroconf.asyncio import AsyncZeroconf from zeroconf.asyncio import AsyncServiceBrowser, AsyncZeroconf
TYPE_AAAA = 28
class HaZeroconf(Zeroconf): class HaZeroconf(Zeroconf):
@ -19,33 +20,26 @@ class HaZeroconf(Zeroconf):
class HaAsyncZeroconf(AsyncZeroconf): class HaAsyncZeroconf(AsyncZeroconf):
"""Home Assistant version of AsyncZeroconf.""" """Home Assistant version of AsyncZeroconf."""
def __init__( # pylint: disable=super-init-not-called
self, *args: Any, **kwargs: Any
) -> None:
"""Wrap AsyncZeroconf."""
self.zeroconf = HaZeroconf(*args, **kwargs)
self.loop = asyncio.get_running_loop()
async def async_close(self) -> None: async def async_close(self) -> None:
"""Fake method to avoid integrations closing it.""" """Fake method to avoid integrations closing it."""
ha_async_close = AsyncZeroconf.async_close
class HaServiceBrowser(ServiceBrowser):
class HaAsyncServiceBrowser(AsyncServiceBrowser):
"""ServiceBrowser that only consumes DNSPointer records.""" """ServiceBrowser that only consumes DNSPointer records."""
def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None: def __init__(self, ipv6: bool, *args: Any, **kwargs: Any) -> None:
"""Pre-Filter update_record to DNSPointers for the configured type.""" """Create service browser that filters ipv6 if it is disabled."""
self.ipv6 = ipv6
super().__init__(*args, **kwargs)
# def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None:
# Each ServerBrowser currently runs in its own thread which """Pre-Filter AAAA records if IPv6 is not enabled."""
# processes every A or AAAA record update per instance. if (
# not self.ipv6
# As the list of zeroconf names we watch for grows, each additional and isinstance(record, DNSAddress)
# ServiceBrowser would process all the A and AAAA updates on the network. and record.type == TYPE_AAAA
# ):
# To avoid overwhelming the system we pre-filter here and only process
# DNSPointers for the configured record name (type)
#
if record.name not in self.types or not isinstance(record, DNSPointer):
return return
super().update_record(zc, now, record) super().update_record(zc, now, record)

View File

@ -33,7 +33,7 @@ sqlalchemy==1.4.17
voluptuous-serialize==2.4.0 voluptuous-serialize==2.4.0
voluptuous==0.12.1 voluptuous==0.12.1
yarl==1.6.3 yarl==1.6.3
zeroconf==0.31.0 zeroconf==0.32.0
pycryptodome>=3.6.6 pycryptodome>=3.6.6

View File

@ -2425,7 +2425,7 @@ zeep[async]==4.0.0
zengge==0.2 zengge==0.2
# homeassistant.components.zeroconf # homeassistant.components.zeroconf
zeroconf==0.31.0 zeroconf==0.32.0
# homeassistant.components.zha # homeassistant.components.zha
zha-quirks==0.0.57 zha-quirks==0.0.57

View File

@ -1328,7 +1328,7 @@ yeelight==0.6.3
zeep[async]==4.0.0 zeep[async]==4.0.0
# homeassistant.components.zeroconf # homeassistant.components.zeroconf
zeroconf==0.31.0 zeroconf==0.32.0
# homeassistant.components.zha # homeassistant.components.zha
zha-quirks==0.0.57 zha-quirks==0.0.57

View File

@ -0,0 +1,15 @@
"""Tests for the Zeroconf component."""
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_async_zeroconf():
"""Mock AsyncZeroconf."""
with patch("homeassistant.components.zeroconf.HaAsyncZeroconf") as mock_aiozc:
zc = mock_aiozc.return_value
zc.async_register_service = AsyncMock()
zc.zeroconf.async_wait_for_start = AsyncMock()
zc.ha_async_close = AsyncMock()
yield zc

View File

@ -1,7 +1,8 @@
"""Test Zeroconf component setup process.""" """Test Zeroconf component setup process."""
from unittest.mock import call, patch from unittest.mock import call, patch
from zeroconf import InterfaceChoice, IPVersion, ServiceInfo, ServiceStateChange from zeroconf import InterfaceChoice, IPVersion, ServiceStateChange
from zeroconf.asyncio import AsyncServiceInfo
from homeassistant.components import zeroconf from homeassistant.components import zeroconf
from homeassistant.components.zeroconf import CONF_DEFAULT_INTERFACE, CONF_IPV6 from homeassistant.components.zeroconf import CONF_DEFAULT_INTERFACE, CONF_IPV6
@ -24,29 +25,8 @@ PROPERTIES = {
HOMEKIT_STATUS_UNPAIRED = b"1" HOMEKIT_STATUS_UNPAIRED = b"1"
HOMEKIT_STATUS_PAIRED = b"0" HOMEKIT_STATUS_PAIRED = b"0"
_ROUTE_NO_LOOPBACK = (
{
"attrs": [
("RTA_TABLE", 254),
("RTA_DST", "224.0.0.251"),
("RTA_OIF", 4),
("RTA_PREFSRC", "192.168.1.5"),
],
},
)
_ROUTE_LOOPBACK = (
{
"attrs": [
("RTA_TABLE", 254),
("RTA_DST", "224.0.0.251"),
("RTA_OIF", 4),
("RTA_PREFSRC", "127.0.0.1"),
],
},
)
def service_update_mock(ipv6, zeroconf, services, handlers, *, limit_service=None):
def service_update_mock(zeroconf, services, handlers, *, limit_service=None):
"""Call service update handler.""" """Call service update handler."""
for service in services: for service in services:
if limit_service is not None and service != limit_service: if limit_service is not None and service != limit_service:
@ -56,7 +36,7 @@ def service_update_mock(zeroconf, services, handlers, *, limit_service=None):
def get_service_info_mock(service_type, name): def get_service_info_mock(service_type, name):
"""Return service info for get_service_info.""" """Return service info for get_service_info."""
return ServiceInfo( return AsyncServiceInfo(
service_type, service_type,
name, name,
addresses=[b"\n\x00\x00\x14"], addresses=[b"\n\x00\x00\x14"],
@ -70,7 +50,7 @@ def get_service_info_mock(service_type, name):
def get_service_info_mock_without_an_address(service_type, name): def get_service_info_mock_without_an_address(service_type, name):
"""Return service info for get_service_info without any addresses.""" """Return service info for get_service_info without any addresses."""
return ServiceInfo( return AsyncServiceInfo(
service_type, service_type,
name, name,
addresses=[], addresses=[],
@ -86,7 +66,7 @@ def get_homekit_info_mock(model, pairing_status):
"""Return homekit info for get_service_info for an homekit device.""" """Return homekit info for get_service_info for an homekit device."""
def mock_homekit_info(service_type, name): def mock_homekit_info(service_type, name):
return ServiceInfo( return AsyncServiceInfo(
service_type, service_type,
name, name,
addresses=[b"\n\x00\x00\x14"], addresses=[b"\n\x00\x00\x14"],
@ -104,7 +84,7 @@ def get_zeroconf_info_mock(macaddress):
"""Return info for get_service_info for an zeroconf device.""" """Return info for get_service_info for an zeroconf device."""
def mock_zc_info(service_type, name): def mock_zc_info(service_type, name):
return ServiceInfo( return AsyncServiceInfo(
service_type, service_type,
name, name,
addresses=[b"\n\x00\x00\x14"], addresses=[b"\n\x00\x00\x14"],
@ -122,7 +102,7 @@ def get_zeroconf_info_mock_manufacturer(manufacturer):
"""Return info for get_service_info for an zeroconf device.""" """Return info for get_service_info for an zeroconf device."""
def mock_zc_info(service_type, name): def mock_zc_info(service_type, name):
return ServiceInfo( return AsyncServiceInfo(
service_type, service_type,
name, name,
addresses=[b"\n\x00\x00\x14"], addresses=[b"\n\x00\x00\x14"],
@ -136,14 +116,14 @@ def get_zeroconf_info_mock_manufacturer(manufacturer):
return mock_zc_info return mock_zc_info
async def test_setup(hass, mock_zeroconf): async def test_setup(hass, mock_async_zeroconf):
"""Test configured options for a device are loaded via config entry.""" """Test configured options for a device are loaded via config entry."""
with patch.object( with patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_service_info_mock, side_effect=get_service_info_mock,
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -162,13 +142,15 @@ async def test_setup(hass, mock_zeroconf):
# Test instance is set. # Test instance is set.
assert "zeroconf" in hass.data assert "zeroconf" in hass.data
assert await hass.components.zeroconf.async_get_instance() is mock_zeroconf assert (
await hass.components.zeroconf.async_get_async_instance() is mock_async_zeroconf
)
async def test_setup_with_overly_long_url_and_name(hass, mock_zeroconf, caplog): async def test_setup_with_overly_long_url_and_name(hass, mock_async_zeroconf, caplog):
"""Test we still setup with long urls and names.""" """Test we still setup with long urls and names."""
with patch.object(hass.config_entries.flow, "async_init"), patch.object( with patch.object(hass.config_entries.flow, "async_init"), patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
), patch( ), patch(
"homeassistant.components.zeroconf.get_url", "homeassistant.components.zeroconf.get_url",
return_value="https://this.url.is.way.too.long/very/deep/path/that/will/make/us/go/over/the/maximum/string/length/and/would/cause/zeroconf/to/fail/to/startup/because/the/key/and/value/can/only/be/255/bytes/and/this/string/is/a/bit/longer/than/the/maximum/length/that/we/allow/for/a/value", return_value="https://this.url.is.way.too.long/very/deep/path/that/will/make/us/go/over/the/maximum/string/length/and/would/cause/zeroconf/to/fail/to/startup/because/the/key/and/value/can/only/be/255/bytes/and/this/string/is/a/bit/longer/than/the/maximum/length/that/we/allow/for/a/value",
@ -177,7 +159,7 @@ async def test_setup_with_overly_long_url_and_name(hass, mock_zeroconf, caplog):
"location_name", "location_name",
"\u00dcBER \u00dcber German Umlaut long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string", "\u00dcBER \u00dcber German Umlaut long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string long string",
), patch( ), patch(
"homeassistant.components.zeroconf.ServiceInfo.request", "homeassistant.components.zeroconf.AsyncServiceInfo.request",
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_START) hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
@ -187,12 +169,12 @@ async def test_setup_with_overly_long_url_and_name(hass, mock_zeroconf, caplog):
assert "German Umlaut" in caplog.text assert "German Umlaut" in caplog.text
async def test_setup_with_default_interface(hass, mock_zeroconf): async def test_setup_with_default_interface(hass, mock_async_zeroconf):
"""Test default interface config.""" """Test default interface config."""
with patch.object(hass.config_entries.flow, "async_init"), patch.object( with patch.object(hass.config_entries.flow, "async_init"), patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
), patch( ), patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_service_info_mock, side_effect=get_service_info_mock,
): ):
assert await async_setup_component( assert await async_setup_component(
@ -201,30 +183,30 @@ async def test_setup_with_default_interface(hass, mock_zeroconf):
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_zeroconf.called_with(interface_choice=InterfaceChoice.Default) assert mock_async_zeroconf.called_with(interface_choice=InterfaceChoice.Default)
async def test_setup_without_default_interface(hass, mock_zeroconf): async def test_setup_without_default_interface(hass, mock_async_zeroconf):
"""Test without default interface config.""" """Test without default interface config."""
with patch.object(hass.config_entries.flow, "async_init"), patch.object( with patch.object(hass.config_entries.flow, "async_init"), patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
), patch( ), patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_service_info_mock, side_effect=get_service_info_mock,
): ):
assert await async_setup_component( assert await async_setup_component(
hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {CONF_DEFAULT_INTERFACE: False}} hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {CONF_DEFAULT_INTERFACE: False}}
) )
assert mock_zeroconf.called_with() assert mock_async_zeroconf.called_with()
async def test_setup_without_ipv6(hass, mock_zeroconf): async def test_setup_without_ipv6(hass, mock_async_zeroconf):
"""Test without ipv6.""" """Test without ipv6."""
with patch.object(hass.config_entries.flow, "async_init"), patch.object( with patch.object(hass.config_entries.flow, "async_init"), patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
), patch( ), patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_service_info_mock, side_effect=get_service_info_mock,
): ):
assert await async_setup_component( assert await async_setup_component(
@ -233,15 +215,15 @@ async def test_setup_without_ipv6(hass, mock_zeroconf):
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_zeroconf.called_with(ip_version=IPVersion.V4Only) assert mock_async_zeroconf.called_with(ip_version=IPVersion.V4Only)
async def test_setup_with_ipv6(hass, mock_zeroconf): async def test_setup_with_ipv6(hass, mock_async_zeroconf):
"""Test without ipv6.""" """Test without ipv6."""
with patch.object(hass.config_entries.flow, "async_init"), patch.object( with patch.object(hass.config_entries.flow, "async_init"), patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
), patch( ), patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_service_info_mock, side_effect=get_service_info_mock,
): ):
assert await async_setup_component( assert await async_setup_component(
@ -250,28 +232,28 @@ async def test_setup_with_ipv6(hass, mock_zeroconf):
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_zeroconf.called_with() assert mock_async_zeroconf.called_with()
async def test_setup_with_ipv6_default(hass, mock_zeroconf): async def test_setup_with_ipv6_default(hass, mock_async_zeroconf):
"""Test without ipv6 as default.""" """Test without ipv6 as default."""
with patch.object(hass.config_entries.flow, "async_init"), patch.object( with patch.object(hass.config_entries.flow, "async_init"), patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
), patch( ), patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_service_info_mock, side_effect=get_service_info_mock,
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_zeroconf.called_with() assert mock_async_zeroconf.called_with()
async def test_zeroconf_match_macaddress(hass, mock_zeroconf): async def test_zeroconf_match_macaddress(hass, mock_async_zeroconf):
"""Test configured options for a device are loaded via config entry.""" """Test configured options for a device are loaded via config entry."""
def http_only_service_update_mock(zeroconf, services, handlers): def http_only_service_update_mock(ipv6, zeroconf, services, handlers):
"""Call service update handler.""" """Call service update handler."""
handlers[0]( handlers[0](
zeroconf, zeroconf,
@ -291,9 +273,9 @@ async def test_zeroconf_match_macaddress(hass, mock_zeroconf):
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=http_only_service_update_mock
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_zeroconf_info_mock("FFAADDCC11DD"), side_effect=get_zeroconf_info_mock("FFAADDCC11DD"),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -305,10 +287,10 @@ async def test_zeroconf_match_macaddress(hass, mock_zeroconf):
assert mock_config_flow.mock_calls[0][1][0] == "shelly" assert mock_config_flow.mock_calls[0][1][0] == "shelly"
async def test_zeroconf_match_manufacturer(hass, mock_zeroconf): async def test_zeroconf_match_manufacturer(hass, mock_async_zeroconf):
"""Test configured options for a device are loaded via config entry.""" """Test configured options for a device are loaded via config entry."""
def http_only_service_update_mock(zeroconf, services, handlers): def http_only_service_update_mock(ipv6, zeroconf, services, handlers):
"""Call service update handler.""" """Call service update handler."""
handlers[0]( handlers[0](
zeroconf, zeroconf,
@ -324,9 +306,9 @@ async def test_zeroconf_match_manufacturer(hass, mock_zeroconf):
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=http_only_service_update_mock
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_zeroconf_info_mock_manufacturer("Samsung Electronics"), side_effect=get_zeroconf_info_mock_manufacturer("Samsung Electronics"),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -338,10 +320,10 @@ async def test_zeroconf_match_manufacturer(hass, mock_zeroconf):
assert mock_config_flow.mock_calls[0][1][0] == "samsungtv" assert mock_config_flow.mock_calls[0][1][0] == "samsungtv"
async def test_zeroconf_match_manufacturer_not_present(hass, mock_zeroconf): async def test_zeroconf_match_manufacturer_not_present(hass, mock_async_zeroconf):
"""Test matchers reject when a property is missing.""" """Test matchers reject when a property is missing."""
def http_only_service_update_mock(zeroconf, services, handlers): def http_only_service_update_mock(ipv6, zeroconf, services, handlers):
"""Call service update handler.""" """Call service update handler."""
handlers[0]( handlers[0](
zeroconf, zeroconf,
@ -357,9 +339,9 @@ async def test_zeroconf_match_manufacturer_not_present(hass, mock_zeroconf):
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=http_only_service_update_mock
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_zeroconf_info_mock("aabbccddeeff"), side_effect=get_zeroconf_info_mock("aabbccddeeff"),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -370,10 +352,10 @@ async def test_zeroconf_match_manufacturer_not_present(hass, mock_zeroconf):
assert len(mock_config_flow.mock_calls) == 0 assert len(mock_config_flow.mock_calls) == 0
async def test_zeroconf_no_match(hass, mock_zeroconf): async def test_zeroconf_no_match(hass, mock_async_zeroconf):
"""Test configured options for a device are loaded via config entry.""" """Test configured options for a device are loaded via config entry."""
def http_only_service_update_mock(zeroconf, services, handlers): def http_only_service_update_mock(ipv6, zeroconf, services, handlers):
"""Call service update handler.""" """Call service update handler."""
handlers[0]( handlers[0](
zeroconf, zeroconf,
@ -389,9 +371,9 @@ async def test_zeroconf_no_match(hass, mock_zeroconf):
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=http_only_service_update_mock
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_zeroconf_info_mock("FFAADDCC11DD"), side_effect=get_zeroconf_info_mock("FFAADDCC11DD"),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -402,10 +384,10 @@ async def test_zeroconf_no_match(hass, mock_zeroconf):
assert len(mock_config_flow.mock_calls) == 0 assert len(mock_config_flow.mock_calls) == 0
async def test_zeroconf_no_match_manufacturer(hass, mock_zeroconf): async def test_zeroconf_no_match_manufacturer(hass, mock_async_zeroconf):
"""Test configured options for a device are loaded via config entry.""" """Test configured options for a device are loaded via config entry."""
def http_only_service_update_mock(zeroconf, services, handlers): def http_only_service_update_mock(ipv6, zeroconf, services, handlers):
"""Call service update handler.""" """Call service update handler."""
handlers[0]( handlers[0](
zeroconf, zeroconf,
@ -421,9 +403,9 @@ async def test_zeroconf_no_match_manufacturer(hass, mock_zeroconf):
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=http_only_service_update_mock
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_zeroconf_info_mock_manufacturer("Not Samsung Electronics"), side_effect=get_zeroconf_info_mock_manufacturer("Not Samsung Electronics"),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -434,7 +416,7 @@ async def test_zeroconf_no_match_manufacturer(hass, mock_zeroconf):
assert len(mock_config_flow.mock_calls) == 0 assert len(mock_config_flow.mock_calls) == 0
async def test_homekit_match_partial_space(hass, mock_zeroconf): async def test_homekit_match_partial_space(hass, mock_async_zeroconf):
"""Test configured options for a device are loaded via config entry.""" """Test configured options for a device are loaded via config entry."""
with patch.dict( with patch.dict(
zc_gen.ZEROCONF, zc_gen.ZEROCONF,
@ -444,12 +426,12 @@ async def test_homekit_match_partial_space(hass, mock_zeroconf):
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, zeroconf,
"HaServiceBrowser", "HaAsyncServiceBrowser",
side_effect=lambda *args, **kwargs: service_update_mock( side_effect=lambda *args, **kwargs: service_update_mock(
*args, **kwargs, limit_service="_hap._tcp.local." *args, **kwargs, limit_service="_hap._tcp.local."
), ),
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_homekit_info_mock("LIFX bulb", HOMEKIT_STATUS_UNPAIRED), side_effect=get_homekit_info_mock("LIFX bulb", HOMEKIT_STATUS_UNPAIRED),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -461,7 +443,7 @@ async def test_homekit_match_partial_space(hass, mock_zeroconf):
assert mock_config_flow.mock_calls[0][1][0] == "lifx" assert mock_config_flow.mock_calls[0][1][0] == "lifx"
async def test_homekit_match_partial_dash(hass, mock_zeroconf): async def test_homekit_match_partial_dash(hass, mock_async_zeroconf):
"""Test configured options for a device are loaded via config entry.""" """Test configured options for a device are loaded via config entry."""
with patch.dict( with patch.dict(
zc_gen.ZEROCONF, zc_gen.ZEROCONF,
@ -471,12 +453,12 @@ async def test_homekit_match_partial_dash(hass, mock_zeroconf):
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, zeroconf,
"HaServiceBrowser", "HaAsyncServiceBrowser",
side_effect=lambda *args, **kwargs: service_update_mock( side_effect=lambda *args, **kwargs: service_update_mock(
*args, **kwargs, limit_service="_hap._udp.local." *args, **kwargs, limit_service="_hap._udp.local."
), ),
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_homekit_info_mock("Rachio-fa46ba", HOMEKIT_STATUS_UNPAIRED), side_effect=get_homekit_info_mock("Rachio-fa46ba", HOMEKIT_STATUS_UNPAIRED),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -488,7 +470,7 @@ async def test_homekit_match_partial_dash(hass, mock_zeroconf):
assert mock_config_flow.mock_calls[0][1][0] == "rachio" assert mock_config_flow.mock_calls[0][1][0] == "rachio"
async def test_homekit_match_partial_fnmatch(hass, mock_zeroconf): async def test_homekit_match_partial_fnmatch(hass, mock_async_zeroconf):
"""Test matching homekit devices with fnmatch.""" """Test matching homekit devices with fnmatch."""
with patch.dict( with patch.dict(
zc_gen.ZEROCONF, zc_gen.ZEROCONF,
@ -498,12 +480,12 @@ async def test_homekit_match_partial_fnmatch(hass, mock_zeroconf):
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, zeroconf,
"HaServiceBrowser", "HaAsyncServiceBrowser",
side_effect=lambda *args, **kwargs: service_update_mock( side_effect=lambda *args, **kwargs: service_update_mock(
*args, **kwargs, limit_service="_hap._tcp.local." *args, **kwargs, limit_service="_hap._tcp.local."
), ),
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_homekit_info_mock("YLDP13YL", HOMEKIT_STATUS_UNPAIRED), side_effect=get_homekit_info_mock("YLDP13YL", HOMEKIT_STATUS_UNPAIRED),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -515,7 +497,7 @@ async def test_homekit_match_partial_fnmatch(hass, mock_zeroconf):
assert mock_config_flow.mock_calls[0][1][0] == "yeelight" assert mock_config_flow.mock_calls[0][1][0] == "yeelight"
async def test_homekit_match_full(hass, mock_zeroconf): async def test_homekit_match_full(hass, mock_async_zeroconf):
"""Test configured options for a device are loaded via config entry.""" """Test configured options for a device are loaded via config entry."""
with patch.dict( with patch.dict(
zc_gen.ZEROCONF, zc_gen.ZEROCONF,
@ -525,12 +507,12 @@ async def test_homekit_match_full(hass, mock_zeroconf):
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, zeroconf,
"HaServiceBrowser", "HaAsyncServiceBrowser",
side_effect=lambda *args, **kwargs: service_update_mock( side_effect=lambda *args, **kwargs: service_update_mock(
*args, **kwargs, limit_service="_hap._udp.local." *args, **kwargs, limit_service="_hap._udp.local."
), ),
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_homekit_info_mock("BSB002", HOMEKIT_STATUS_UNPAIRED), side_effect=get_homekit_info_mock("BSB002", HOMEKIT_STATUS_UNPAIRED),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -542,7 +524,7 @@ async def test_homekit_match_full(hass, mock_zeroconf):
assert mock_config_flow.mock_calls[0][1][0] == "hue" assert mock_config_flow.mock_calls[0][1][0] == "hue"
async def test_homekit_already_paired(hass, mock_zeroconf): async def test_homekit_already_paired(hass, mock_async_zeroconf):
"""Test that an already paired device is sent to homekit_controller.""" """Test that an already paired device is sent to homekit_controller."""
with patch.dict( with patch.dict(
zc_gen.ZEROCONF, zc_gen.ZEROCONF,
@ -552,12 +534,12 @@ async def test_homekit_already_paired(hass, mock_zeroconf):
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, zeroconf,
"HaServiceBrowser", "HaAsyncServiceBrowser",
side_effect=lambda *args, **kwargs: service_update_mock( side_effect=lambda *args, **kwargs: service_update_mock(
*args, **kwargs, limit_service="_hap._tcp.local." *args, **kwargs, limit_service="_hap._tcp.local."
), ),
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_homekit_info_mock("tado", HOMEKIT_STATUS_PAIRED), side_effect=get_homekit_info_mock("tado", HOMEKIT_STATUS_PAIRED),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -570,7 +552,7 @@ async def test_homekit_already_paired(hass, mock_zeroconf):
assert mock_config_flow.mock_calls[1][1][0] == "homekit_controller" assert mock_config_flow.mock_calls[1][1][0] == "homekit_controller"
async def test_homekit_invalid_paring_status(hass, mock_zeroconf): async def test_homekit_invalid_paring_status(hass, mock_async_zeroconf):
"""Test that missing paring data is not sent to homekit_controller.""" """Test that missing paring data is not sent to homekit_controller."""
with patch.dict( with patch.dict(
zc_gen.ZEROCONF, zc_gen.ZEROCONF,
@ -580,12 +562,12 @@ async def test_homekit_invalid_paring_status(hass, mock_zeroconf):
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, zeroconf,
"HaServiceBrowser", "HaAsyncServiceBrowser",
side_effect=lambda *args, **kwargs: service_update_mock( side_effect=lambda *args, **kwargs: service_update_mock(
*args, **kwargs, limit_service="_hap._tcp.local." *args, **kwargs, limit_service="_hap._tcp.local."
), ),
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_homekit_info_mock("tado", b"invalid"), side_effect=get_homekit_info_mock("tado", b"invalid"),
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -597,7 +579,7 @@ async def test_homekit_invalid_paring_status(hass, mock_zeroconf):
assert mock_config_flow.mock_calls[0][1][0] == "tado" assert mock_config_flow.mock_calls[0][1][0] == "tado"
async def test_homekit_not_paired(hass, mock_zeroconf): async def test_homekit_not_paired(hass, mock_async_zeroconf):
"""Test that an not paired device is sent to homekit_controller.""" """Test that an not paired device is sent to homekit_controller."""
with patch.dict( with patch.dict(
zc_gen.ZEROCONF, zc_gen.ZEROCONF,
@ -606,9 +588,9 @@ async def test_homekit_not_paired(hass, mock_zeroconf):
), patch.object( ), patch.object(
hass.config_entries.flow, "async_init" hass.config_entries.flow, "async_init"
) as mock_config_flow, patch.object( ) as mock_config_flow, patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
) as mock_service_browser, patch( ) as mock_service_browser, patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_homekit_info_mock( side_effect=get_homekit_info_mock(
"this_will_not_match_any_integration", HOMEKIT_STATUS_UNPAIRED "this_will_not_match_any_integration", HOMEKIT_STATUS_UNPAIRED
), ),
@ -646,19 +628,21 @@ async def test_info_from_service_with_addresses(hass):
assert info is None assert info is None
async def test_get_instance(hass, mock_zeroconf): async def test_get_instance(hass, mock_async_zeroconf):
"""Test we get an instance.""" """Test we get an instance."""
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
assert await hass.components.zeroconf.async_get_instance() is mock_zeroconf assert (
await hass.components.zeroconf.async_get_async_instance() is mock_async_zeroconf
)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(mock_zeroconf.ha_close.mock_calls) == 1 assert len(mock_async_zeroconf.ha_async_close.mock_calls) == 1
async def test_removed_ignored(hass, mock_zeroconf): async def test_removed_ignored(hass, mock_async_zeroconf):
"""Test we remove it when a zeroconf entry is removed.""" """Test we remove it when a zeroconf entry is removed."""
def service_update_mock(zeroconf, services, handlers): def service_update_mock(ipv6, zeroconf, services, handlers):
"""Call service update handler.""" """Call service update handler."""
handlers[0]( handlers[0](
zeroconf, zeroconf,
@ -680,9 +664,9 @@ async def test_removed_ignored(hass, mock_zeroconf):
) )
with patch.object( with patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
), patch( ), patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_service_info_mock, side_effect=get_service_info_mock,
) as mock_service_info: ) as mock_service_info:
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
@ -709,24 +693,28 @@ _ADAPTER_WITH_DEFAULT_ENABLED = [
] ]
async def test_async_detect_interfaces_setting_non_loopback_route(hass): async def test_async_detect_interfaces_setting_non_loopback_route(
hass, mock_async_zeroconf
):
"""Test without default interface config and the route returns a non-loopback address.""" """Test without default interface config and the route returns a non-loopback address."""
with patch( with patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, patch.object(
"homeassistant.components.zeroconf.models.HaZeroconf" hass.config_entries.flow, "async_init"
) as mock_zc, patch.object(hass.config_entries.flow, "async_init"), patch.object( ), patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
), patch( ), patch(
"homeassistant.components.zeroconf.network.async_get_adapters", "homeassistant.components.zeroconf.network.async_get_adapters",
return_value=_ADAPTER_WITH_DEFAULT_ENABLED, return_value=_ADAPTER_WITH_DEFAULT_ENABLED,
), patch( ), patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_service_info_mock, side_effect=get_service_info_mock,
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_zc.mock_calls[0] == call(interfaces=InterfaceChoice.Default) assert mock_zc.mock_calls[0] == call(
interfaces=InterfaceChoice.Default, ip_version=IPVersion.V4Only
)
_ADAPTERS_WITH_MANUAL_CONFIG = [ _ADAPTERS_WITH_MANUAL_CONFIG = [
@ -764,17 +752,17 @@ _ADAPTERS_WITH_MANUAL_CONFIG = [
] ]
async def test_async_detect_interfaces_setting_empty_route(hass): async def test_async_detect_interfaces_setting_empty_route(hass, mock_async_zeroconf):
"""Test without default interface config and the route returns nothing.""" """Test without default interface config and the route returns nothing."""
with patch( with patch("homeassistant.components.zeroconf.HaZeroconf") as mock_zc, patch.object(
"homeassistant.components.zeroconf.models.HaZeroconf" hass.config_entries.flow, "async_init"
) as mock_zc, patch.object(hass.config_entries.flow, "async_init"), patch.object( ), patch.object(
zeroconf, "HaServiceBrowser", side_effect=service_update_mock zeroconf, "HaAsyncServiceBrowser", side_effect=service_update_mock
), patch( ), patch(
"homeassistant.components.zeroconf.network.async_get_adapters", "homeassistant.components.zeroconf.network.async_get_adapters",
return_value=_ADAPTERS_WITH_MANUAL_CONFIG, return_value=_ADAPTERS_WITH_MANUAL_CONFIG,
), patch( ), patch(
"homeassistant.components.zeroconf.ServiceInfo", "homeassistant.components.zeroconf.AsyncServiceInfo",
side_effect=get_service_info_mock, side_effect=get_service_info_mock,
): ):
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})

View File

@ -10,7 +10,9 @@ from homeassistant.setup import async_setup_component
DOMAIN = "zeroconf" DOMAIN = "zeroconf"
async def test_multiple_zeroconf_instances(hass, mock_zeroconf, caplog): async def test_multiple_zeroconf_instances(
hass, mock_async_zeroconf, mock_zeroconf, caplog
):
"""Test creating multiple zeroconf throws without an integration.""" """Test creating multiple zeroconf throws without an integration."""
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})
@ -24,7 +26,9 @@ async def test_multiple_zeroconf_instances(hass, mock_zeroconf, caplog):
assert "Zeroconf" in caplog.text assert "Zeroconf" in caplog.text
async def test_multiple_zeroconf_instances_gives_shared(hass, mock_zeroconf, caplog): async def test_multiple_zeroconf_instances_gives_shared(
hass, mock_async_zeroconf, mock_zeroconf, caplog
):
"""Test creating multiple zeroconf gives the shared instance to an integration.""" """Test creating multiple zeroconf gives the shared instance to an integration."""
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}})