Enable strict typing for zeroconf (#48450)

* Enable strict typing for zeroconf

* Fix lutron_caseta

* Fix pylint warning

* Fix tests

* Fix xiaomi_aqara test

* Add __init__.py in homeassistant.generated module

* Restore add_job with type: ignore
This commit is contained in:
Ruslan Sayfutdinov 2021-03-30 17:48:04 +01:00 committed by GitHub
parent 338be8c70b
commit 82c94826fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 111 additions and 92 deletions

View File

@ -10,7 +10,6 @@ from pylutron_caseta.smartbridge import Smartbridge
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.zeroconf import ATTR_HOSTNAME
from homeassistant.const import CONF_HOST, CONF_NAME from homeassistant.const import CONF_HOST, CONF_NAME
from homeassistant.core import callback from homeassistant.core import callback
@ -66,7 +65,7 @@ class LutronCasetaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_zeroconf(self, discovery_info): async def async_step_zeroconf(self, discovery_info):
"""Handle a flow initialized by zeroconf discovery.""" """Handle a flow initialized by zeroconf discovery."""
hostname = discovery_info[ATTR_HOSTNAME] hostname = discovery_info["hostname"]
if hostname is None or not hostname.startswith("lutron-"): if hostname is None or not hostname.startswith("lutron-"):
return self.async_abort(reason="not_lutron_device") return self.async_abort(reason="not_lutron_device")

View File

@ -7,16 +7,14 @@ from functools import partial
import ipaddress import ipaddress
import logging import logging
import socket import socket
from typing import Any, TypedDict
import voluptuous as vol import voluptuous as vol
from zeroconf import ( from zeroconf import (
DNSPointer,
DNSRecord,
Error as ZeroconfError, Error as ZeroconfError,
InterfaceChoice, InterfaceChoice,
IPVersion, IPVersion,
NonUniqueNameException, NonUniqueNameException,
ServiceBrowser,
ServiceInfo, ServiceInfo,
ServiceStateChange, ServiceStateChange,
Zeroconf, Zeroconf,
@ -24,29 +22,24 @@ from zeroconf import (
from homeassistant import util from homeassistant import util
from homeassistant.const import ( from homeassistant.const import (
ATTR_NAME,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STARTED,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
__version__, __version__,
) )
from homeassistant.core import Event, HomeAssistant
import homeassistant.helpers.config_validation as cv 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.helpers.singleton import singleton from homeassistant.helpers.singleton import singleton
from homeassistant.loader import async_get_homekit, async_get_zeroconf from homeassistant.loader import async_get_homekit, async_get_zeroconf
from .models import HaServiceBrowser, HaZeroconf
from .usage import install_multiple_zeroconf_catcher from .usage import install_multiple_zeroconf_catcher
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = "zeroconf" DOMAIN = "zeroconf"
ATTR_HOST = "host"
ATTR_PORT = "port"
ATTR_HOSTNAME = "hostname"
ATTR_TYPE = "type"
ATTR_PROPERTIES = "properties"
ZEROCONF_TYPE = "_home-assistant._tcp.local." ZEROCONF_TYPE = "_home-assistant._tcp.local."
HOMEKIT_TYPES = [ HOMEKIT_TYPES = [
"_hap._tcp.local.", "_hap._tcp.local.",
@ -59,7 +52,6 @@ CONF_IPV6 = "ipv6"
DEFAULT_DEFAULT_INTERFACE = True DEFAULT_DEFAULT_INTERFACE = True
DEFAULT_IPV6 = True DEFAULT_IPV6 = True
HOMEKIT_PROPERTIES = "properties"
HOMEKIT_PAIRED_STATUS_FLAG = "sf" HOMEKIT_PAIRED_STATUS_FLAG = "sf"
HOMEKIT_MODEL = "md" HOMEKIT_MODEL = "md"
@ -85,20 +77,31 @@ CONFIG_SCHEMA = vol.Schema(
) )
class HaServiceInfo(TypedDict):
"""Prepared info from mDNS entries."""
host: str
port: int | None
hostname: str
type: str
name: str
properties: dict[str, Any]
@singleton(DOMAIN) @singleton(DOMAIN)
async def async_get_instance(hass): async def async_get_instance(hass: HomeAssistant) -> HaZeroconf:
"""Zeroconf instance to be shared with other integrations that use it.""" """Zeroconf instance to be shared with other integrations that use it."""
return await _async_get_instance(hass) return await _async_get_instance(hass)
async def _async_get_instance(hass, **zcargs): async def _async_get_instance(hass: HomeAssistant, **zcargs: Any) -> HaZeroconf:
logging.getLogger("zeroconf").setLevel(logging.NOTSET) logging.getLogger("zeroconf").setLevel(logging.NOTSET)
zeroconf = await hass.async_add_executor_job(partial(HaZeroconf, **zcargs)) zeroconf = await hass.async_add_executor_job(partial(HaZeroconf, **zcargs))
install_multiple_zeroconf_catcher(zeroconf) install_multiple_zeroconf_catcher(zeroconf)
def _stop_zeroconf(_): def _stop_zeroconf(_event: Event) -> None:
"""Stop Zeroconf.""" """Stop Zeroconf."""
zeroconf.ha_close() zeroconf.ha_close()
@ -107,40 +110,10 @@ async def _async_get_instance(hass, **zcargs):
return zeroconf return zeroconf
class HaServiceBrowser(ServiceBrowser): async def async_setup(hass: HomeAssistant, config: dict) -> bool:
"""ServiceBrowser that only consumes DNSPointer records."""
def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None:
"""Pre-Filter update_record to DNSPointers for the configured type."""
#
# Each ServerBrowser currently runs in its own thread which
# processes every A or AAAA record update per instance.
#
# As the list of zeroconf names we watch for grows, each additional
# ServiceBrowser would process all the A and AAAA updates on the network.
#
# To avoid overwhemling 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
super().update_record(zc, now, record)
class HaZeroconf(Zeroconf):
"""Zeroconf that cannot be closed."""
def close(self):
"""Fake method to avoid integrations closing it."""
ha_close = Zeroconf.close
async def async_setup(hass, config):
"""Set up Zeroconf and make Home Assistant discoverable.""" """Set up Zeroconf and make Home Assistant discoverable."""
zc_config = config.get(DOMAIN, {}) zc_config = config.get(DOMAIN, {})
zc_args = {} zc_args: dict = {}
if zc_config.get(CONF_DEFAULT_INTERFACE, DEFAULT_DEFAULT_INTERFACE): if zc_config.get(CONF_DEFAULT_INTERFACE, DEFAULT_DEFAULT_INTERFACE):
zc_args["interfaces"] = InterfaceChoice.Default zc_args["interfaces"] = InterfaceChoice.Default
if not zc_config.get(CONF_IPV6, DEFAULT_IPV6): if not zc_config.get(CONF_IPV6, DEFAULT_IPV6):
@ -148,7 +121,7 @@ async def async_setup(hass, config):
zeroconf = hass.data[DOMAIN] = await _async_get_instance(hass, **zc_args) zeroconf = hass.data[DOMAIN] = await _async_get_instance(hass, **zc_args)
async def _async_zeroconf_hass_start(_event): async def _async_zeroconf_hass_start(_event: Event) -> None:
"""Expose Home Assistant on zeroconf when it starts. """Expose Home Assistant on zeroconf when it starts.
Wait till started or otherwise HTTP is not up and running. Wait till started or otherwise HTTP is not up and running.
@ -158,7 +131,7 @@ async def async_setup(hass, config):
_register_hass_zc_service, hass, zeroconf, uuid _register_hass_zc_service, hass, zeroconf, uuid
) )
async def _async_zeroconf_hass_started(_event): async def _async_zeroconf_hass_started(_event: Event) -> None:
"""Start the service browser.""" """Start the service browser."""
await _async_start_zeroconf_browser(hass, zeroconf) await _async_start_zeroconf_browser(hass, zeroconf)
@ -171,7 +144,9 @@ async def async_setup(hass, config):
return True return True
def _register_hass_zc_service(hass, zeroconf, uuid): def _register_hass_zc_service(
hass: HomeAssistant, zeroconf: HaZeroconf, uuid: str
) -> None:
# Get instance UUID # Get instance UUID
valid_location_name = _truncate_location_name_to_valid(hass.config.location_name) valid_location_name = _truncate_location_name_to_valid(hass.config.location_name)
@ -224,7 +199,9 @@ def _register_hass_zc_service(hass, zeroconf, uuid):
) )
async def _async_start_zeroconf_browser(hass, zeroconf): async def _async_start_zeroconf_browser(
hass: HomeAssistant, zeroconf: HaZeroconf
) -> None:
"""Start the zeroconf browser.""" """Start the zeroconf browser."""
zeroconf_types = await async_get_zeroconf(hass) zeroconf_types = await async_get_zeroconf(hass)
@ -236,7 +213,12 @@ async def _async_start_zeroconf_browser(hass, zeroconf):
if hk_type not in zeroconf_types: if hk_type not in zeroconf_types:
types.append(hk_type) types.append(hk_type)
def service_update(zeroconf, service_type, name, state_change): def service_update(
zeroconf: Zeroconf,
service_type: str,
name: str,
state_change: ServiceStateChange,
) -> None:
"""Service state changed.""" """Service state changed."""
nonlocal zeroconf_types nonlocal zeroconf_types
nonlocal homekit_models nonlocal homekit_models
@ -276,12 +258,11 @@ async def _async_start_zeroconf_browser(hass, zeroconf):
# offering a second discovery for the same device # offering a second discovery for the same device
if ( if (
discovery_was_forwarded discovery_was_forwarded
and HOMEKIT_PROPERTIES in info and HOMEKIT_PAIRED_STATUS_FLAG in info["properties"]
and HOMEKIT_PAIRED_STATUS_FLAG in info[HOMEKIT_PROPERTIES]
): ):
try: try:
# 0 means paired and not discoverable by iOS clients) # 0 means paired and not discoverable by iOS clients)
if int(info[HOMEKIT_PROPERTIES][HOMEKIT_PAIRED_STATUS_FLAG]): if int(info["properties"][HOMEKIT_PAIRED_STATUS_FLAG]):
return return
except ValueError: except ValueError:
# HomeKit pairing status unknown # HomeKit pairing status unknown
@ -289,12 +270,12 @@ async def _async_start_zeroconf_browser(hass, zeroconf):
return return
if "name" in info: if "name" in info:
lowercase_name = info["name"].lower() lowercase_name: str | None = info["name"].lower()
else: else:
lowercase_name = None lowercase_name = None
if "macaddress" in info.get("properties", {}): if "macaddress" in info["properties"]:
uppercase_mac = info["properties"]["macaddress"].upper() uppercase_mac: str | None = info["properties"]["macaddress"].upper()
else: else:
uppercase_mac = None uppercase_mac = None
@ -318,20 +299,22 @@ async def _async_start_zeroconf_browser(hass, zeroconf):
hass.add_job( hass.add_job(
hass.config_entries.flow.async_init( hass.config_entries.flow.async_init(
entry["domain"], context={"source": DOMAIN}, data=info entry["domain"], context={"source": DOMAIN}, data=info
) ) # type: ignore
) )
_LOGGER.debug("Starting Zeroconf browser") _LOGGER.debug("Starting Zeroconf browser")
HaServiceBrowser(zeroconf, types, handlers=[service_update]) HaServiceBrowser(zeroconf, types, handlers=[service_update])
def handle_homekit(hass, homekit_models, info) -> bool: def handle_homekit(
hass: HomeAssistant, homekit_models: dict[str, str], info: HaServiceInfo
) -> bool:
"""Handle a HomeKit discovery. """Handle a HomeKit discovery.
Return if discovery was forwarded. Return if discovery was forwarded.
""" """
model = None model = None
props = info.get(HOMEKIT_PROPERTIES, {}) props = info["properties"]
for key in props: for key in props:
if key.lower() == HOMEKIT_MODEL: if key.lower() == HOMEKIT_MODEL:
@ -352,16 +335,16 @@ def handle_homekit(hass, homekit_models, info) -> bool:
hass.add_job( hass.add_job(
hass.config_entries.flow.async_init( hass.config_entries.flow.async_init(
homekit_models[test_model], context={"source": "homekit"}, data=info homekit_models[test_model], context={"source": "homekit"}, data=info
) ) # type: ignore
) )
return True return True
return False return False
def info_from_service(service): def info_from_service(service: ServiceInfo) -> HaServiceInfo | None:
"""Return prepared info from mDNS entries.""" """Return prepared info from mDNS entries."""
properties = {"_raw": {}} properties: dict[str, Any] = {"_raw": {}}
for key, value in service.properties.items(): for key, value in service.properties.items():
# See https://ietf.org/rfc/rfc6763.html#section-6.4 and # See https://ietf.org/rfc/rfc6763.html#section-6.4 and
@ -386,19 +369,17 @@ def info_from_service(service):
address = service.addresses[0] address = service.addresses[0]
info = { return {
ATTR_HOST: str(ipaddress.ip_address(address)), "host": str(ipaddress.ip_address(address)),
ATTR_PORT: service.port, "port": service.port,
ATTR_HOSTNAME: service.server, "hostname": service.server,
ATTR_TYPE: service.type, "type": service.type,
ATTR_NAME: service.name, "name": service.name,
ATTR_PROPERTIES: properties, "properties": properties,
} }
return info
def _suppress_invalid_properties(properties: dict) -> None:
def _suppress_invalid_properties(properties):
"""Suppress any properties that will cause zeroconf to fail to startup.""" """Suppress any properties that will cause zeroconf to fail to startup."""
for prop, prop_value in properties.items(): for prop, prop_value in properties.items():
@ -415,7 +396,7 @@ def _suppress_invalid_properties(properties):
properties[prop] = "" properties[prop] = ""
def _truncate_location_name_to_valid(location_name): def _truncate_location_name_to_valid(location_name: str) -> str:
"""Truncate or return the location name usable for zeroconf.""" """Truncate or return the location name usable for zeroconf."""
if len(location_name.encode("utf-8")) < MAX_NAME_LEN: if len(location_name.encode("utf-8")) < MAX_NAME_LEN:
return location_name return location_name

View File

@ -0,0 +1,33 @@
"""Models for Zeroconf."""
from zeroconf import DNSPointer, DNSRecord, ServiceBrowser, Zeroconf
class HaZeroconf(Zeroconf):
"""Zeroconf that cannot be closed."""
def close(self) -> None:
"""Fake method to avoid integrations closing it."""
ha_close = Zeroconf.close
class HaServiceBrowser(ServiceBrowser):
"""ServiceBrowser that only consumes DNSPointer records."""
def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None:
"""Pre-Filter update_record to DNSPointers for the configured type."""
#
# Each ServerBrowser currently runs in its own thread which
# processes every A or AAAA record update per instance.
#
# As the list of zeroconf names we watch for grows, each additional
# ServiceBrowser would process all the A and AAAA updates on the network.
#
# To avoid overwhemling 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
super().update_record(zc, now, record)

View File

@ -2,6 +2,7 @@
from contextlib import suppress from contextlib import suppress
import logging import logging
from typing import Any
import zeroconf import zeroconf
@ -11,23 +12,25 @@ from homeassistant.helpers.frame import (
report_integration, report_integration,
) )
from .models import HaZeroconf
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def install_multiple_zeroconf_catcher(hass_zc) -> None: def install_multiple_zeroconf_catcher(hass_zc: HaZeroconf) -> None:
"""Wrap the Zeroconf class to return the shared instance if multiple instances are detected.""" """Wrap the Zeroconf class to return the shared instance if multiple instances are detected."""
def new_zeroconf_new(self, *k, **kw): def new_zeroconf_new(self: zeroconf.Zeroconf, *k: Any, **kw: Any) -> HaZeroconf:
_report( _report(
"attempted to create another Zeroconf instance. Please use the shared Zeroconf via await homeassistant.components.zeroconf.async_get_instance(hass)", "attempted to create another Zeroconf instance. Please use the shared Zeroconf via await homeassistant.components.zeroconf.async_get_instance(hass)",
) )
return hass_zc return hass_zc
def new_zeroconf_init(self, *k, **kw): def new_zeroconf_init(self: zeroconf.Zeroconf, *k: Any, **kw: Any) -> None:
return return
zeroconf.Zeroconf.__new__ = new_zeroconf_new zeroconf.Zeroconf.__new__ = new_zeroconf_new # type: ignore
zeroconf.Zeroconf.__init__ = new_zeroconf_init zeroconf.Zeroconf.__init__ = new_zeroconf_init # type: ignore
def _report(what: str) -> None: def _report(what: str) -> None:

View File

@ -0,0 +1,4 @@
"""All files in this module are automatically generated by hassfest.
To update, run python3 -m script.hassfest
"""

View File

@ -43,7 +43,7 @@ warn_redundant_casts = true
warn_unused_configs = true warn_unused_configs = true
[mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.knx.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*] [mypy-homeassistant.block_async_io,homeassistant.bootstrap,homeassistant.components,homeassistant.config_entries,homeassistant.config,homeassistant.const,homeassistant.core,homeassistant.data_entry_flow,homeassistant.exceptions,homeassistant.__init__,homeassistant.loader,homeassistant.__main__,homeassistant.requirements,homeassistant.runner,homeassistant.setup,homeassistant.util,homeassistant.auth.*,homeassistant.components.automation.*,homeassistant.components.binary_sensor.*,homeassistant.components.bond.*,homeassistant.components.calendar.*,homeassistant.components.cover.*,homeassistant.components.device_automation.*,homeassistant.components.frontend.*,homeassistant.components.geo_location.*,homeassistant.components.group.*,homeassistant.components.history.*,homeassistant.components.http.*,homeassistant.components.huawei_lte.*,homeassistant.components.hyperion.*,homeassistant.components.image_processing.*,homeassistant.components.integration.*,homeassistant.components.knx.*,homeassistant.components.light.*,homeassistant.components.lock.*,homeassistant.components.mailbox.*,homeassistant.components.media_player.*,homeassistant.components.notify.*,homeassistant.components.number.*,homeassistant.components.persistent_notification.*,homeassistant.components.proximity.*,homeassistant.components.recorder.purge,homeassistant.components.recorder.repack,homeassistant.components.remote.*,homeassistant.components.scene.*,homeassistant.components.sensor.*,homeassistant.components.slack.*,homeassistant.components.sun.*,homeassistant.components.switch.*,homeassistant.components.systemmonitor.*,homeassistant.components.tts.*,homeassistant.components.vacuum.*,homeassistant.components.water_heater.*,homeassistant.components.weather.*,homeassistant.components.websocket_api.*,homeassistant.components.zeroconf.*,homeassistant.components.zone.*,homeassistant.components.zwave_js.*,homeassistant.helpers.*,homeassistant.scripts.*,homeassistant.util.*,tests.components.hyperion.*]
strict = true strict = true
ignore_errors = false ignore_errors = false
warn_unreachable = true warn_unreachable = true

View File

@ -17,11 +17,12 @@ from homeassistant.components.lutron_caseta.const import (
ERROR_CANNOT_CONNECT, ERROR_CANNOT_CONNECT,
STEP_IMPORT_FAILED, STEP_IMPORT_FAILED,
) )
from homeassistant.components.zeroconf import ATTR_HOSTNAME
from homeassistant.const import CONF_HOST from homeassistant.const import CONF_HOST
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
ATTR_HOSTNAME = "hostname"
EMPTY_MOCK_CONFIG_ENTRY = { EMPTY_MOCK_CONFIG_ENTRY = {
CONF_HOST: "", CONF_HOST: "",
CONF_KEYFILE: "", CONF_KEYFILE: "",

View File

@ -5,7 +5,6 @@ from unittest.mock import Mock, patch
import pytest import pytest
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.components.xiaomi_aqara import config_flow, const from homeassistant.components.xiaomi_aqara import config_flow, const
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, CONF_PROTOCOL from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PORT, CONF_PROTOCOL
@ -402,7 +401,7 @@ async def test_zeroconf_success(hass):
const.DOMAIN, const.DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
data={ data={
zeroconf.ATTR_HOST: TEST_HOST, CONF_HOST: TEST_HOST,
ZEROCONF_NAME: TEST_ZEROCONF_NAME, ZEROCONF_NAME: TEST_ZEROCONF_NAME,
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
}, },
@ -444,7 +443,7 @@ async def test_zeroconf_missing_data(hass):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
const.DOMAIN, const.DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
data={zeroconf.ATTR_HOST: TEST_HOST, ZEROCONF_NAME: TEST_ZEROCONF_NAME}, data={CONF_HOST: TEST_HOST, ZEROCONF_NAME: TEST_ZEROCONF_NAME},
) )
assert result["type"] == "abort" assert result["type"] == "abort"
@ -457,7 +456,7 @@ async def test_zeroconf_unknown_device(hass):
const.DOMAIN, const.DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
data={ data={
zeroconf.ATTR_HOST: TEST_HOST, CONF_HOST: TEST_HOST,
ZEROCONF_NAME: "not-a-xiaomi-aqara-gateway", ZEROCONF_NAME: "not-a-xiaomi-aqara-gateway",
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
}, },

View File

@ -4,7 +4,6 @@ from unittest.mock import Mock, patch
from miio import DeviceException from miio import DeviceException
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.components.xiaomi_miio import const from homeassistant.components.xiaomi_miio import const
from homeassistant.components.xiaomi_miio.config_flow import DEFAULT_GATEWAY_NAME from homeassistant.components.xiaomi_miio.config_flow import DEFAULT_GATEWAY_NAME
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
@ -106,7 +105,7 @@ async def test_zeroconf_gateway_success(hass):
const.DOMAIN, const.DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
data={ data={
zeroconf.ATTR_HOST: TEST_HOST, CONF_HOST: TEST_HOST,
ZEROCONF_NAME: TEST_ZEROCONF_NAME, ZEROCONF_NAME: TEST_ZEROCONF_NAME,
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
}, },
@ -146,7 +145,7 @@ async def test_zeroconf_unknown_device(hass):
const.DOMAIN, const.DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
data={ data={
zeroconf.ATTR_HOST: TEST_HOST, CONF_HOST: TEST_HOST,
ZEROCONF_NAME: "not-a-xiaomi-miio-device", ZEROCONF_NAME: "not-a-xiaomi-miio-device",
ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC}, ZEROCONF_PROP: {ZEROCONF_MAC: TEST_MAC},
}, },
@ -171,7 +170,7 @@ async def test_zeroconf_missing_data(hass):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
const.DOMAIN, const.DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
data={zeroconf.ATTR_HOST: TEST_HOST, ZEROCONF_NAME: TEST_ZEROCONF_NAME}, data={CONF_HOST: TEST_HOST, ZEROCONF_NAME: TEST_ZEROCONF_NAME},
) )
assert result["type"] == "abort" assert result["type"] == "abort"
@ -342,7 +341,7 @@ async def zeroconf_device_success(hass, zeroconf_name_to_test, model_to_test):
const.DOMAIN, const.DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
data={ data={
zeroconf.ATTR_HOST: TEST_HOST, CONF_HOST: TEST_HOST,
ZEROCONF_NAME: zeroconf_name_to_test, ZEROCONF_NAME: zeroconf_name_to_test,
ZEROCONF_PROP: {"poch": f"0:mac={TEST_MAC_DEVICE}\x00"}, ZEROCONF_PROP: {"poch": f"0:mac={TEST_MAC_DEVICE}\x00"},
}, },