Limit zeroconf discovery to name/macaddress when provided (#39877)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>pull/39938/head
parent
487a74ba5d
commit
9389a7c9be
|
@ -4,7 +4,11 @@
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/axis",
|
"documentation": "https://www.home-assistant.io/integrations/axis",
|
||||||
"requirements": ["axis==35"],
|
"requirements": ["axis==35"],
|
||||||
"zeroconf": ["_axis-video._tcp.local."],
|
"zeroconf": [
|
||||||
|
{"type":"_axis-video._tcp.local.","macaddress":"00408C*"},
|
||||||
|
{"type":"_axis-video._tcp.local.","macaddress":"ACCC8E*"},
|
||||||
|
{"type":"_axis-video._tcp.local.","macaddress":"B8A44F*"}
|
||||||
|
],
|
||||||
"after_dependencies": ["mqtt"],
|
"after_dependencies": ["mqtt"],
|
||||||
"codeowners": ["@Kane610"]
|
"codeowners": ["@Kane610"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/brother",
|
"documentation": "https://www.home-assistant.io/integrations/brother",
|
||||||
"codeowners": ["@bieniu"],
|
"codeowners": ["@bieniu"],
|
||||||
"requirements": ["brother==0.1.17"],
|
"requirements": ["brother==0.1.17"],
|
||||||
"zeroconf": ["_printer._tcp.local."],
|
"zeroconf": [{"type": "_printer._tcp.local.", "name":"brother*"}],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"quality_scale": "platinum"
|
"quality_scale": "platinum"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/doorbird",
|
"documentation": "https://www.home-assistant.io/integrations/doorbird",
|
||||||
"requirements": ["doorbirdpy==2.1.0"],
|
"requirements": ["doorbirdpy==2.1.0"],
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
"zeroconf": ["_axis-video._tcp.local."],
|
"zeroconf": [{"type":"_axis-video._tcp.local.","macaddress":"1CCAE3*"}],
|
||||||
"codeowners": ["@oblogic7", "@bdraco"],
|
"codeowners": ["@oblogic7", "@bdraco"],
|
||||||
"config_flow": true
|
"config_flow": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/shelly",
|
"documentation": "https://www.home-assistant.io/integrations/shelly",
|
||||||
"requirements": ["aioshelly==0.3.1"],
|
"requirements": ["aioshelly==0.3.1"],
|
||||||
"zeroconf": ["_http._tcp.local."],
|
"zeroconf": [{"type": "_http._tcp.local.", "name":"shelly*"}],
|
||||||
"codeowners": ["@balloob", "@bieniu"]
|
"codeowners": ["@balloob", "@bieniu"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"@bsmappee"
|
"@bsmappee"
|
||||||
],
|
],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
"_ssh._tcp.local."
|
{"type":"_ssh._tcp.local.", "name":"smappee1*"},
|
||||||
|
{"type":"_ssh._tcp.local.", "name":"smappee2*"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Support for exposing Home Assistant via Zeroconf."""
|
"""Support for exposing Home Assistant via Zeroconf."""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import fnmatch
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
@ -268,10 +269,26 @@ def setup(hass, config):
|
||||||
# likely bad homekit data
|
# likely bad homekit data
|
||||||
return
|
return
|
||||||
|
|
||||||
for domain in zeroconf_types[service_type]:
|
for entry in zeroconf_types[service_type]:
|
||||||
|
if len(entry) > 1:
|
||||||
|
if "macaddress" in entry:
|
||||||
|
if "properties" not in info:
|
||||||
|
continue
|
||||||
|
if "macaddress" not in info["properties"]:
|
||||||
|
continue
|
||||||
|
if not fnmatch.fnmatch(
|
||||||
|
info["properties"]["macaddress"], entry["macaddress"]
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
if "name" in entry:
|
||||||
|
if "name" not in info:
|
||||||
|
continue
|
||||||
|
if not fnmatch.fnmatch(info["name"], entry["name"]):
|
||||||
|
continue
|
||||||
|
|
||||||
hass.add_job(
|
hass.add_job(
|
||||||
hass.config_entries.flow.async_init(
|
hass.config_entries.flow.async_init(
|
||||||
domain, context={"source": DOMAIN}, data=info
|
entry["domain"], context={"source": DOMAIN}, data=info
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -7,75 +7,142 @@ To update, run python3 -m script.hassfest
|
||||||
|
|
||||||
ZEROCONF = {
|
ZEROCONF = {
|
||||||
"_Volumio._tcp.local.": [
|
"_Volumio._tcp.local.": [
|
||||||
"volumio"
|
{
|
||||||
|
"domain": "volumio"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_api._udp.local.": [
|
"_api._udp.local.": [
|
||||||
"guardian"
|
{
|
||||||
|
"domain": "guardian"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_axis-video._tcp.local.": [
|
"_axis-video._tcp.local.": [
|
||||||
"axis",
|
{
|
||||||
"doorbird"
|
"domain": "axis",
|
||||||
|
"macaddress": "00408C*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "axis",
|
||||||
|
"macaddress": "ACCC8E*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "axis",
|
||||||
|
"macaddress": "B8A44F*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "doorbird",
|
||||||
|
"macaddress": "1CCAE3*"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_bond._tcp.local.": [
|
"_bond._tcp.local.": [
|
||||||
"bond"
|
{
|
||||||
|
"domain": "bond"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_daap._tcp.local.": [
|
"_daap._tcp.local.": [
|
||||||
"forked_daapd"
|
{
|
||||||
|
"domain": "forked_daapd"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_dkapi._tcp.local.": [
|
"_dkapi._tcp.local.": [
|
||||||
"daikin"
|
{
|
||||||
|
"domain": "daikin"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_elg._tcp.local.": [
|
"_elg._tcp.local.": [
|
||||||
"elgato"
|
{
|
||||||
|
"domain": "elgato"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_esphomelib._tcp.local.": [
|
"_esphomelib._tcp.local.": [
|
||||||
"esphome"
|
{
|
||||||
|
"domain": "esphome"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_googlecast._tcp.local.": [
|
"_googlecast._tcp.local.": [
|
||||||
"cast"
|
{
|
||||||
|
"domain": "cast"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_hap._tcp.local.": [
|
"_hap._tcp.local.": [
|
||||||
"homekit_controller"
|
{
|
||||||
|
"domain": "homekit_controller"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_homekit._tcp.local.": [
|
"_homekit._tcp.local.": [
|
||||||
"homekit"
|
{
|
||||||
|
"domain": "homekit"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_http._tcp.local.": [
|
"_http._tcp.local.": [
|
||||||
"shelly"
|
{
|
||||||
|
"domain": "shelly",
|
||||||
|
"name": "shelly*"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_ipp._tcp.local.": [
|
"_ipp._tcp.local.": [
|
||||||
"ipp"
|
{
|
||||||
|
"domain": "ipp"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_ipps._tcp.local.": [
|
"_ipps._tcp.local.": [
|
||||||
"ipp"
|
{
|
||||||
|
"domain": "ipp"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_miio._udp.local.": [
|
"_miio._udp.local.": [
|
||||||
"xiaomi_aqara",
|
{
|
||||||
"xiaomi_miio"
|
"domain": "xiaomi_aqara"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "xiaomi_miio"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_nut._tcp.local.": [
|
"_nut._tcp.local.": [
|
||||||
"nut"
|
{
|
||||||
|
"domain": "nut"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_plugwise._tcp.local.": [
|
"_plugwise._tcp.local.": [
|
||||||
"plugwise"
|
{
|
||||||
|
"domain": "plugwise"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_printer._tcp.local.": [
|
"_printer._tcp.local.": [
|
||||||
"brother"
|
{
|
||||||
|
"domain": "brother",
|
||||||
|
"name": "brother*"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_spotify-connect._tcp.local.": [
|
"_spotify-connect._tcp.local.": [
|
||||||
"spotify"
|
{
|
||||||
|
"domain": "spotify"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_ssh._tcp.local.": [
|
"_ssh._tcp.local.": [
|
||||||
"smappee"
|
{
|
||||||
|
"domain": "smappee",
|
||||||
|
"name": "smappee1*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "smappee",
|
||||||
|
"name": "smappee2*"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_viziocast._tcp.local.": [
|
"_viziocast._tcp.local.": [
|
||||||
"vizio"
|
{
|
||||||
|
"domain": "vizio"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_wled._tcp.local.": [
|
"_wled._tcp.local.": [
|
||||||
"wled"
|
{
|
||||||
|
"domain": "wled"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"_xbmc-jsonrpc-h._tcp.local.": [
|
"_xbmc-jsonrpc-h._tcp.local.": [
|
||||||
"kodi"
|
{
|
||||||
|
"domain": "kodi"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,18 +145,25 @@ async def async_get_config_flows(hass: "HomeAssistant") -> Set[str]:
|
||||||
return flows
|
return flows
|
||||||
|
|
||||||
|
|
||||||
async def async_get_zeroconf(hass: "HomeAssistant") -> Dict[str, List]:
|
async def async_get_zeroconf(hass: "HomeAssistant") -> Dict[str, List[Dict[str, str]]]:
|
||||||
"""Return cached list of zeroconf types."""
|
"""Return cached list of zeroconf types."""
|
||||||
zeroconf: Dict[str, List] = ZEROCONF.copy()
|
zeroconf: Dict[str, List[Dict[str, str]]] = ZEROCONF.copy()
|
||||||
|
|
||||||
integrations = await async_get_custom_components(hass)
|
integrations = await async_get_custom_components(hass)
|
||||||
for integration in integrations.values():
|
for integration in integrations.values():
|
||||||
if not integration.zeroconf:
|
if not integration.zeroconf:
|
||||||
continue
|
continue
|
||||||
for typ in integration.zeroconf:
|
for entry in integration.zeroconf:
|
||||||
zeroconf.setdefault(typ, [])
|
data = {"domain": integration.domain}
|
||||||
if integration.domain not in zeroconf[typ]:
|
if isinstance(entry, dict):
|
||||||
zeroconf[typ].append(integration.domain)
|
typ = entry["type"]
|
||||||
|
entry_without_type = entry.copy()
|
||||||
|
del entry_without_type["type"]
|
||||||
|
data.update(entry_without_type)
|
||||||
|
else:
|
||||||
|
typ = entry
|
||||||
|
|
||||||
|
zeroconf.setdefault(typ, []).append(data)
|
||||||
|
|
||||||
return zeroconf
|
return zeroconf
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,18 @@ MANIFEST_SCHEMA = vol.Schema(
|
||||||
vol.Required("domain"): str,
|
vol.Required("domain"): str,
|
||||||
vol.Required("name"): str,
|
vol.Required("name"): str,
|
||||||
vol.Optional("config_flow"): bool,
|
vol.Optional("config_flow"): bool,
|
||||||
vol.Optional("zeroconf"): [str],
|
vol.Optional("zeroconf"): [
|
||||||
|
vol.Any(
|
||||||
|
str,
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required("type"): str,
|
||||||
|
vol.Optional("macaddress"): str,
|
||||||
|
vol.Optional("name"): str,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
vol.Optional("ssdp"): vol.Schema(
|
vol.Optional("ssdp"): vol.Schema(
|
||||||
vol.All([vol.All(vol.Schema({}, extra=vol.ALLOW_EXTRA), vol.Length(min=1))])
|
vol.All([vol.All(vol.Schema({}, extra=vol.ALLOW_EXTRA), vol.Length(min=1))])
|
||||||
),
|
),
|
||||||
|
|
|
@ -37,8 +37,17 @@ def generate_and_validate(integrations: Dict[str, Integration]):
|
||||||
if not (service_types or homekit_models):
|
if not (service_types or homekit_models):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for service_type in service_types:
|
for entry in service_types:
|
||||||
service_type_dict[service_type].append(domain)
|
data = {"domain": domain}
|
||||||
|
if isinstance(entry, dict):
|
||||||
|
typ = entry["type"]
|
||||||
|
entry_without_type = entry.copy()
|
||||||
|
del entry_without_type["type"]
|
||||||
|
data.update(entry_without_type)
|
||||||
|
else:
|
||||||
|
typ = entry
|
||||||
|
|
||||||
|
service_type_dict[typ].append(data)
|
||||||
|
|
||||||
for model in homekit_models:
|
for model in homekit_models:
|
||||||
if model in homekit_dict:
|
if model in homekit_dict:
|
||||||
|
|
|
@ -79,6 +79,24 @@ def get_homekit_info_mock(model, pairing_status):
|
||||||
return mock_homekit_info
|
return mock_homekit_info
|
||||||
|
|
||||||
|
|
||||||
|
def get_zeroconf_info_mock(macaddress):
|
||||||
|
"""Return info for get_service_info for an zeroconf device."""
|
||||||
|
|
||||||
|
def mock_zc_info(service_type, name):
|
||||||
|
return ServiceInfo(
|
||||||
|
service_type,
|
||||||
|
name,
|
||||||
|
addresses=[b"\n\x00\x00\x14"],
|
||||||
|
port=80,
|
||||||
|
weight=0,
|
||||||
|
priority=0,
|
||||||
|
server="name.local.",
|
||||||
|
properties={b"macaddress": macaddress.encode()},
|
||||||
|
)
|
||||||
|
|
||||||
|
return mock_zc_info
|
||||||
|
|
||||||
|
|
||||||
async def test_setup(hass, mock_zeroconf):
|
async def test_setup(hass, mock_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(
|
||||||
|
@ -94,7 +112,11 @@ async def test_setup(hass, mock_zeroconf):
|
||||||
assert len(mock_service_browser.mock_calls) == 1
|
assert len(mock_service_browser.mock_calls) == 1
|
||||||
expected_flow_calls = 0
|
expected_flow_calls = 0
|
||||||
for matching_components in zc_gen.ZEROCONF.values():
|
for matching_components in zc_gen.ZEROCONF.values():
|
||||||
expected_flow_calls += len(matching_components)
|
domains = set()
|
||||||
|
for component in matching_components:
|
||||||
|
if len(component) == 1:
|
||||||
|
domains.add(component["domain"])
|
||||||
|
expected_flow_calls += len(domains)
|
||||||
assert len(mock_config_flow.mock_calls) == expected_flow_calls
|
assert len(mock_config_flow.mock_calls) == expected_flow_calls
|
||||||
|
|
||||||
# Test instance is set.
|
# Test instance is set.
|
||||||
|
@ -209,10 +231,77 @@ async def test_service_with_invalid_name(hass, mock_zeroconf, caplog):
|
||||||
assert "Failed to get info for device name" in caplog.text
|
assert "Failed to get info for device name" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_zeroconf_match(hass, mock_zeroconf):
|
||||||
|
"""Test configured options for a device are loaded via config entry."""
|
||||||
|
|
||||||
|
def http_only_service_update_mock(zeroconf, services, handlers):
|
||||||
|
"""Call service update handler."""
|
||||||
|
handlers[0](
|
||||||
|
zeroconf,
|
||||||
|
"_http._tcp.local.",
|
||||||
|
"shelly108._http._tcp.local.",
|
||||||
|
ServiceStateChange.Added,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.dict(
|
||||||
|
zc_gen.ZEROCONF,
|
||||||
|
{"_http._tcp.local.": [{"domain": "shelly", "name": "shelly*"}]},
|
||||||
|
clear=True,
|
||||||
|
), patch.object(
|
||||||
|
hass.config_entries.flow, "async_init"
|
||||||
|
) as mock_config_flow, patch.object(
|
||||||
|
zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock
|
||||||
|
) as mock_service_browser:
|
||||||
|
mock_zeroconf.get_service_info.side_effect = get_zeroconf_info_mock(
|
||||||
|
"FFAADDCC11DD"
|
||||||
|
)
|
||||||
|
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_service_browser.mock_calls) == 1
|
||||||
|
assert len(mock_config_flow.mock_calls) == 1
|
||||||
|
assert mock_config_flow.mock_calls[0][1][0] == "shelly"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_zeroconf_no_match(hass, mock_zeroconf):
|
||||||
|
"""Test configured options for a device are loaded via config entry."""
|
||||||
|
|
||||||
|
def http_only_service_update_mock(zeroconf, services, handlers):
|
||||||
|
"""Call service update handler."""
|
||||||
|
handlers[0](
|
||||||
|
zeroconf,
|
||||||
|
"_http._tcp.local.",
|
||||||
|
"somethingelse._http._tcp.local.",
|
||||||
|
ServiceStateChange.Added,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.dict(
|
||||||
|
zc_gen.ZEROCONF,
|
||||||
|
{"_http._tcp.local.": [{"domain": "shelly", "name": "shelly*"}]},
|
||||||
|
clear=True,
|
||||||
|
), patch.object(
|
||||||
|
hass.config_entries.flow, "async_init"
|
||||||
|
) as mock_config_flow, patch.object(
|
||||||
|
zeroconf, "HaServiceBrowser", side_effect=http_only_service_update_mock
|
||||||
|
) as mock_service_browser:
|
||||||
|
mock_zeroconf.get_service_info.side_effect = get_zeroconf_info_mock(
|
||||||
|
"FFAADDCC11DD"
|
||||||
|
)
|
||||||
|
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_service_browser.mock_calls) == 1
|
||||||
|
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_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, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True
|
zc_gen.ZEROCONF,
|
||||||
|
{zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]},
|
||||||
|
clear=True,
|
||||||
), 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(
|
||||||
|
@ -233,7 +322,9 @@ async def test_homekit_match_partial_space(hass, mock_zeroconf):
|
||||||
async def test_homekit_match_partial_dash(hass, mock_zeroconf):
|
async def test_homekit_match_partial_dash(hass, mock_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, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True
|
zc_gen.ZEROCONF,
|
||||||
|
{zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]},
|
||||||
|
clear=True,
|
||||||
), 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(
|
||||||
|
@ -254,7 +345,9 @@ async def test_homekit_match_partial_dash(hass, mock_zeroconf):
|
||||||
async def test_homekit_match_full(hass, mock_zeroconf):
|
async def test_homekit_match_full(hass, mock_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, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True
|
zc_gen.ZEROCONF,
|
||||||
|
{zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]},
|
||||||
|
clear=True,
|
||||||
), 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(
|
||||||
|
@ -267,11 +360,6 @@ async def test_homekit_match_full(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()
|
||||||
|
|
||||||
homekit_mock = get_homekit_info_mock("BSB002", HOMEKIT_STATUS_UNPAIRED)
|
|
||||||
info = homekit_mock("_hap._tcp.local.", "BSB002._hap._tcp.local.")
|
|
||||||
import pprint
|
|
||||||
|
|
||||||
pprint.pprint(["homekit", info])
|
|
||||||
assert len(mock_service_browser.mock_calls) == 1
|
assert len(mock_service_browser.mock_calls) == 1
|
||||||
assert len(mock_config_flow.mock_calls) == 1
|
assert len(mock_config_flow.mock_calls) == 1
|
||||||
assert mock_config_flow.mock_calls[0][1][0] == "hue"
|
assert mock_config_flow.mock_calls[0][1][0] == "hue"
|
||||||
|
@ -280,7 +368,9 @@ async def test_homekit_match_full(hass, mock_zeroconf):
|
||||||
async def test_homekit_already_paired(hass, mock_zeroconf):
|
async def test_homekit_already_paired(hass, mock_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, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True
|
zc_gen.ZEROCONF,
|
||||||
|
{zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]},
|
||||||
|
clear=True,
|
||||||
), 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(
|
||||||
|
@ -302,7 +392,9 @@ async def test_homekit_already_paired(hass, mock_zeroconf):
|
||||||
async def test_homekit_invalid_paring_status(hass, mock_zeroconf):
|
async def test_homekit_invalid_paring_status(hass, mock_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, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True
|
zc_gen.ZEROCONF,
|
||||||
|
{zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]},
|
||||||
|
clear=True,
|
||||||
), 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(
|
||||||
|
@ -323,7 +415,9 @@ async def test_homekit_invalid_paring_status(hass, mock_zeroconf):
|
||||||
async def test_homekit_not_paired(hass, mock_zeroconf):
|
async def test_homekit_not_paired(hass, mock_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, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True
|
zc_gen.ZEROCONF,
|
||||||
|
{zeroconf.HOMEKIT_TYPE: [{"domain": "homekit_controller"}]},
|
||||||
|
clear=True,
|
||||||
), 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(
|
||||||
|
|
|
@ -218,6 +218,23 @@ def test_integration_properties(hass):
|
||||||
assert integration.zeroconf is None
|
assert integration.zeroconf is None
|
||||||
assert integration.ssdp is None
|
assert integration.ssdp is None
|
||||||
|
|
||||||
|
integration = loader.Integration(
|
||||||
|
hass,
|
||||||
|
"custom_components.hue",
|
||||||
|
None,
|
||||||
|
{
|
||||||
|
"name": "Philips Hue",
|
||||||
|
"domain": "hue",
|
||||||
|
"dependencies": ["test-dep"],
|
||||||
|
"zeroconf": [{"type": "_hue._tcp.local.", "name": "hue*"}],
|
||||||
|
"requirements": ["test-req==1.0.0"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert integration.is_built_in is False
|
||||||
|
assert integration.homekit is None
|
||||||
|
assert integration.zeroconf == [{"type": "_hue._tcp.local.", "name": "hue*"}]
|
||||||
|
assert integration.ssdp is None
|
||||||
|
|
||||||
|
|
||||||
async def test_integrations_only_once(hass):
|
async def test_integrations_only_once(hass):
|
||||||
"""Test that we load integrations only once."""
|
"""Test that we load integrations only once."""
|
||||||
|
@ -253,6 +270,25 @@ def _get_test_integration(hass, name, config_flow):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_test_integration_with_zeroconf_matcher(hass, name, config_flow):
|
||||||
|
"""Return a generated test integration with a zeroconf matcher."""
|
||||||
|
return loader.Integration(
|
||||||
|
hass,
|
||||||
|
f"homeassistant.components.{name}",
|
||||||
|
None,
|
||||||
|
{
|
||||||
|
"name": name,
|
||||||
|
"domain": name,
|
||||||
|
"config_flow": config_flow,
|
||||||
|
"dependencies": [],
|
||||||
|
"requirements": [],
|
||||||
|
"zeroconf": [{"type": f"_{name}._tcp.local.", "name": f"{name}*"}],
|
||||||
|
"homekit": {"models": [name]},
|
||||||
|
"ssdp": [{"manufacturer": name, "modelName": name}],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_get_custom_components(hass):
|
async def test_get_custom_components(hass):
|
||||||
"""Verify that custom components are cached."""
|
"""Verify that custom components are cached."""
|
||||||
test_1_integration = _get_test_integration(hass, "test_1", False)
|
test_1_integration = _get_test_integration(hass, "test_1", False)
|
||||||
|
@ -289,7 +325,9 @@ async def test_get_config_flows(hass):
|
||||||
async def test_get_zeroconf(hass):
|
async def test_get_zeroconf(hass):
|
||||||
"""Verify that custom components with zeroconf are found."""
|
"""Verify that custom components with zeroconf are found."""
|
||||||
test_1_integration = _get_test_integration(hass, "test_1", True)
|
test_1_integration = _get_test_integration(hass, "test_1", True)
|
||||||
test_2_integration = _get_test_integration(hass, "test_2", True)
|
test_2_integration = _get_test_integration_with_zeroconf_matcher(
|
||||||
|
hass, "test_2", True
|
||||||
|
)
|
||||||
|
|
||||||
with patch("homeassistant.loader.async_get_custom_components") as mock_get:
|
with patch("homeassistant.loader.async_get_custom_components") as mock_get:
|
||||||
mock_get.return_value = {
|
mock_get.return_value = {
|
||||||
|
@ -297,8 +335,10 @@ async def test_get_zeroconf(hass):
|
||||||
"test_2": test_2_integration,
|
"test_2": test_2_integration,
|
||||||
}
|
}
|
||||||
zeroconf = await loader.async_get_zeroconf(hass)
|
zeroconf = await loader.async_get_zeroconf(hass)
|
||||||
assert zeroconf["_test_1._tcp.local."] == ["test_1"]
|
assert zeroconf["_test_1._tcp.local."] == [{"domain": "test_1"}]
|
||||||
assert zeroconf["_test_2._tcp.local."] == ["test_2"]
|
assert zeroconf["_test_2._tcp.local."] == [
|
||||||
|
{"domain": "test_2", "name": "test_2*"}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
async def test_get_homekit(hass):
|
async def test_get_homekit(hass):
|
||||||
|
|
Loading…
Reference in New Issue