Bump async-upnp-client to 0.25.0 (#66414)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Steven Looman 2022-02-27 19:29:29 +01:00 committed by GitHub
parent 1c0365a72b
commit dbbb5655e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 95 additions and 73 deletions

View File

@ -46,7 +46,9 @@ class DlnaDmrData:
"""Clean up resources when Home Assistant is stopped.""" """Clean up resources when Home Assistant is stopped."""
LOGGER.debug("Cleaning resources in DlnaDmrData") LOGGER.debug("Cleaning resources in DlnaDmrData")
async with self.lock: async with self.lock:
tasks = (server.stop_server() for server in self.event_notifiers.values()) tasks = (
server.async_stop_server() for server in self.event_notifiers.values()
)
asyncio.gather(*tasks) asyncio.gather(*tasks)
self.event_notifiers = {} self.event_notifiers = {}
self.event_notifier_refs = defaultdict(int) self.event_notifier_refs = defaultdict(int)
@ -76,14 +78,14 @@ class DlnaDmrData:
return self.event_notifiers[listen_addr].event_handler return self.event_notifiers[listen_addr].event_handler
# Start event handler # Start event handler
source = (listen_addr.host or "0.0.0.0", listen_addr.port)
server = AiohttpNotifyServer( server = AiohttpNotifyServer(
requester=self.requester, requester=self.requester,
listen_port=listen_addr.port, source=source,
listen_host=listen_addr.host,
callback_url=listen_addr.callback_url, callback_url=listen_addr.callback_url,
loop=hass.loop, loop=hass.loop,
) )
await server.start_server() await server.async_start_server()
LOGGER.debug("Started event handler at %s", server.callback_url) LOGGER.debug("Started event handler at %s", server.callback_url)
self.event_notifiers[listen_addr] = server self.event_notifiers[listen_addr] = server
@ -103,7 +105,7 @@ class DlnaDmrData:
# Shutdown the server when it has no more users # Shutdown the server when it has no more users
if self.event_notifier_refs[listen_addr] == 0: if self.event_notifier_refs[listen_addr] == 0:
server = self.event_notifiers.pop(listen_addr) server = self.event_notifiers.pop(listen_addr)
await server.stop_server() await server.async_stop_server()
# Remove the cleanup listener when there's nothing left to cleanup # Remove the cleanup listener when there's nothing left to cleanup
if not self.event_notifiers: if not self.event_notifiers:

View File

@ -3,7 +3,7 @@
"name": "DLNA Digital Media Renderer", "name": "DLNA Digital Media Renderer",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr", "documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
"requirements": ["async-upnp-client==0.23.5"], "requirements": ["async-upnp-client==0.25.0"],
"dependencies": ["ssdp"], "dependencies": ["ssdp"],
"after_dependencies": ["media_source"], "after_dependencies": ["media_source"],
"ssdp": [ "ssdp": [

View File

@ -7,7 +7,12 @@ from dataclasses import dataclass
import functools import functools
from typing import Any, TypeVar, cast from typing import Any, TypeVar, cast
from async_upnp_client import UpnpEventHandler, UpnpFactory, UpnpRequester from async_upnp_client import (
UpnpEventHandler,
UpnpFactory,
UpnpNotifyServer,
UpnpRequester,
)
from async_upnp_client.aiohttp import AiohttpSessionRequester from async_upnp_client.aiohttp import AiohttpSessionRequester
from async_upnp_client.const import NotificationSubType from async_upnp_client.const import NotificationSubType
from async_upnp_client.exceptions import UpnpActionError, UpnpConnectionError, UpnpError from async_upnp_client.exceptions import UpnpActionError, UpnpConnectionError, UpnpError
@ -68,7 +73,7 @@ class DlnaDmsData:
self.upnp_factory = UpnpFactory(self.requester, non_strict=True) self.upnp_factory = UpnpFactory(self.requester, non_strict=True)
# NOTE: event_handler is not actually used, and is only created to # NOTE: event_handler is not actually used, and is only created to
# satisfy the DmsDevice.__init__ signature # satisfy the DmsDevice.__init__ signature
self.event_handler = UpnpEventHandler("", self.requester) self.event_handler = UpnpEventHandler(UpnpNotifyServer(), self.requester)
self.devices = {} self.devices = {}
self.sources = {} self.sources = {}

View File

@ -3,7 +3,7 @@
"name": "DLNA Digital Media Server", "name": "DLNA Digital Media Server",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/dlna_dms", "documentation": "https://www.home-assistant.io/integrations/dlna_dms",
"requirements": ["async-upnp-client==0.23.5"], "requirements": ["async-upnp-client==0.25.0"],
"dependencies": ["ssdp"], "dependencies": ["ssdp"],
"after_dependencies": ["media_source"], "after_dependencies": ["media_source"],
"ssdp": [ "ssdp": [

View File

@ -60,12 +60,14 @@ async def async_get_enabled_source_ips(
if not adapter["enabled"]: if not adapter["enabled"]:
continue continue
if adapter["ipv4"]: if adapter["ipv4"]:
sources.extend(IPv4Address(ipv4["address"]) for ipv4 in adapter["ipv4"]) addrs_ipv4 = [IPv4Address(ipv4["address"]) for ipv4 in adapter["ipv4"]]
sources.extend(addrs_ipv4)
if adapter["ipv6"]: if adapter["ipv6"]:
# With python 3.9 add scope_ids can be addrs_ipv6 = [
# added by enumerating adapter["ipv6"]s IPv6Address(f"{ipv6['address']}%{ipv6['scope_id']}")
# IPv6Address(f"::%{ipv6['scope_id']}") for ipv6 in adapter["ipv6"]
sources.extend(IPv6Address(ipv6["address"]) for ipv6 in adapter["ipv6"]) ]
sources.extend(addrs_ipv6)
return sources return sources

View File

@ -11,10 +11,15 @@ import logging
from typing import Any from typing import Any
from async_upnp_client.aiohttp import AiohttpSessionRequester from async_upnp_client.aiohttp import AiohttpSessionRequester
from async_upnp_client.const import DeviceOrServiceType, SsdpHeaders, SsdpSource from async_upnp_client.const import (
AddressTupleVXType,
DeviceOrServiceType,
SsdpHeaders,
SsdpSource,
)
from async_upnp_client.description_cache import DescriptionCache from async_upnp_client.description_cache import DescriptionCache
from async_upnp_client.ssdp import SSDP_PORT from async_upnp_client.ssdp import SSDP_PORT, determine_source_target, is_ipv4_address
from async_upnp_client.ssdp_listener import SsdpDevice, SsdpListener from async_upnp_client.ssdp_listener import SsdpDevice, SsdpDeviceTracker, SsdpListener
from async_upnp_client.utils import CaseInsensitiveDict from async_upnp_client.utils import CaseInsensitiveDict
from homeassistant import config_entries from homeassistant import config_entries
@ -372,17 +377,10 @@ class Scanner:
async def _async_build_source_set(self) -> set[IPv4Address | IPv6Address]: async def _async_build_source_set(self) -> set[IPv4Address | IPv6Address]:
"""Build the list of ssdp sources.""" """Build the list of ssdp sources."""
adapters = await network.async_get_adapters(self.hass)
sources: set[IPv4Address | IPv6Address] = set()
if network.async_only_default_interface_enabled(adapters):
sources.add(IPv4Address("0.0.0.0"))
return sources
return { return {
source_ip source_ip
for source_ip in await network.async_get_enabled_source_ips(self.hass) for source_ip in await network.async_get_enabled_source_ips(self.hass)
if not source_ip.is_loopback if not source_ip.is_loopback and not source_ip.is_global
and not (isinstance(source_ip, IPv6Address) and source_ip.is_global)
} }
async def async_scan(self, *_: Any) -> None: async def async_scan(self, *_: Any) -> None:
@ -401,11 +399,8 @@ class Scanner:
# address. This matches pysonos' behavior # address. This matches pysonos' behavior
# https://github.com/amelchio/pysonos/blob/d4329b4abb657d106394ae69357805269708c996/pysonos/discovery.py#L120 # https://github.com/amelchio/pysonos/blob/d4329b4abb657d106394ae69357805269708c996/pysonos/discovery.py#L120
for listener in self._ssdp_listeners: for listener in self._ssdp_listeners:
try: if is_ipv4_address(listener.source):
IPv4Address(listener.source_ip) await listener.async_search((str(IPV4_BROADCAST), SSDP_PORT))
except ValueError:
continue
await listener.async_search((str(IPV4_BROADCAST), SSDP_PORT))
async def async_start(self) -> None: async def async_start(self) -> None:
"""Start the scanners.""" """Start the scanners."""
@ -425,11 +420,26 @@ class Scanner:
async def _async_start_ssdp_listeners(self) -> None: async def _async_start_ssdp_listeners(self) -> None:
"""Start the SSDP Listeners.""" """Start the SSDP Listeners."""
# Devices are shared between all sources.
device_tracker = SsdpDeviceTracker()
for source_ip in await self._async_build_source_set(): for source_ip in await self._async_build_source_set():
source_ip_str = str(source_ip)
if source_ip.version == 6:
source_tuple: AddressTupleVXType = (
source_ip_str,
0,
0,
int(getattr(source_ip, "scope_id")),
)
else:
source_tuple = (source_ip_str, 0)
source, target = determine_source_target(source_tuple)
self._ssdp_listeners.append( self._ssdp_listeners.append(
SsdpListener( SsdpListener(
async_callback=self._ssdp_listener_callback, async_callback=self._ssdp_listener_callback,
source_ip=source_ip, source=source,
target=target,
device_tracker=device_tracker,
) )
) )
results = await asyncio.gather( results = await asyncio.gather(
@ -441,7 +451,7 @@ class Scanner:
if isinstance(result, Exception): if isinstance(result, Exception):
_LOGGER.warning( _LOGGER.warning(
"Failed to setup listener for %s: %s", "Failed to setup listener for %s: %s",
self._ssdp_listeners[idx].source_ip, self._ssdp_listeners[idx].source,
result, result,
) )
failed_listeners.append(self._ssdp_listeners[idx]) failed_listeners.append(self._ssdp_listeners[idx])

View File

@ -2,7 +2,7 @@
"domain": "ssdp", "domain": "ssdp",
"name": "Simple Service Discovery Protocol (SSDP)", "name": "Simple Service Discovery Protocol (SSDP)",
"documentation": "https://www.home-assistant.io/integrations/ssdp", "documentation": "https://www.home-assistant.io/integrations/ssdp",
"requirements": ["async-upnp-client==0.23.5"], "requirements": ["async-upnp-client==0.25.0"],
"dependencies": ["network"], "dependencies": ["network"],
"after_dependencies": ["zeroconf"], "after_dependencies": ["zeroconf"],
"codeowners": [], "codeowners": [],

View File

@ -177,8 +177,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
# Create sensors. # Setup platforms, creating sensors/binary_sensors.
LOGGER.debug("Enabling sensors")
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True return True
@ -188,7 +187,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
"""Unload a UPnP/IGD device from a config entry.""" """Unload a UPnP/IGD device from a config entry."""
LOGGER.debug("Unloading config entry: %s", config_entry.unique_id) LOGGER.debug("Unloading config entry: %s", config_entry.unique_id)
LOGGER.debug("Deleting sensors") # Unload platforms.
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)

View File

@ -28,8 +28,6 @@ async def async_setup_entry(
"""Set up the UPnP/IGD sensors.""" """Set up the UPnP/IGD sensors."""
coordinator = hass.data[DOMAIN][config_entry.entry_id] coordinator = hass.data[DOMAIN][config_entry.entry_id]
LOGGER.debug("Adding binary sensor")
entities = [ entities = [
UpnpStatusBinarySensor( UpnpStatusBinarySensor(
coordinator=coordinator, coordinator=coordinator,
@ -38,7 +36,7 @@ async def async_setup_entry(
for entity_description in BINARYSENSOR_ENTITY_DESCRIPTIONS for entity_description in BINARYSENSOR_ENTITY_DESCRIPTIONS
if coordinator.data.get(entity_description.key) is not None if coordinator.data.get(entity_description.key) is not None
] ]
LOGGER.debug("Adding entities: %s", entities) LOGGER.debug("Adding binary_sensor entities: %s", entities)
async_add_entities(entities) async_add_entities(entities)

View File

@ -222,7 +222,9 @@ class UpnpFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
unique_id = discovery_info.ssdp_usn unique_id = discovery_info.ssdp_usn
await self.async_set_unique_id(unique_id) await self.async_set_unique_id(unique_id)
hostname = discovery_info.ssdp_headers["_host"] hostname = discovery_info.ssdp_headers["_host"]
self._abort_if_unique_id_configured(updates={CONFIG_ENTRY_HOSTNAME: hostname}) self._abort_if_unique_id_configured(
updates={CONFIG_ENTRY_HOSTNAME: hostname}, reload_on_update=False
)
# Handle devices changing their UDN, only allow a single host. # Handle devices changing their UDN, only allow a single host.
existing_entries = self._async_current_entries() existing_entries = self._async_current_entries()

View File

@ -80,6 +80,10 @@ class Device:
if service_info.ssdp_location is None: if service_info.ssdp_location is None:
return return
if change == SsdpChange.ALIVE:
# We care only about updates.
return
device = self._igd_device.device device = self._igd_device.device
if service_info.ssdp_location == device.device_url: if service_info.ssdp_location == device.device_url:
return return

View File

@ -3,7 +3,7 @@
"name": "UPnP/IGD", "name": "UPnP/IGD",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/upnp", "documentation": "https://www.home-assistant.io/integrations/upnp",
"requirements": ["async-upnp-client==0.23.5"], "requirements": ["async-upnp-client==0.25.0"],
"dependencies": ["network", "ssdp"], "dependencies": ["network", "ssdp"],
"codeowners": ["@StevenLooman","@ehendrix23"], "codeowners": ["@StevenLooman","@ehendrix23"],
"ssdp": [ "ssdp": [

View File

@ -136,7 +136,7 @@ async def async_setup_entry(
] ]
) )
LOGGER.debug("Adding entities: %s", entities) LOGGER.debug("Adding sensor entities: %s", entities)
async_add_entities(entities) async_add_entities(entities)

View File

@ -2,7 +2,7 @@
"domain": "yeelight", "domain": "yeelight",
"name": "Yeelight", "name": "Yeelight",
"documentation": "https://www.home-assistant.io/integrations/yeelight", "documentation": "https://www.home-assistant.io/integrations/yeelight",
"requirements": ["yeelight==0.7.9", "async-upnp-client==0.23.5"], "requirements": ["yeelight==0.7.9", "async-upnp-client==0.25.0"],
"codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"], "codeowners": ["@zewelor", "@shenxn", "@starkillerOG", "@alexyao2015"],
"config_flow": true, "config_flow": true,
"dependencies": ["network"], "dependencies": ["network"],

View File

@ -67,12 +67,13 @@ class YeelightScanner:
return _async_connected return _async_connected
source = (str(source_ip), 0)
self._listeners.append( self._listeners.append(
SsdpSearchListener( SsdpSearchListener(
async_callback=self._async_process_entry, async_callback=self._async_process_entry,
service_type=SSDP_ST, service_type=SSDP_ST,
target=SSDP_TARGET, target=SSDP_TARGET,
source_ip=source_ip, source=source,
async_connect_callback=_wrap_async_connected_idx(idx), async_connect_callback=_wrap_async_connected_idx(idx),
) )
) )
@ -87,7 +88,7 @@ class YeelightScanner:
continue continue
_LOGGER.warning( _LOGGER.warning(
"Failed to setup listener for %s: %s", "Failed to setup listener for %s: %s",
self._listeners[idx].source_ip, self._listeners[idx].source,
result, result,
) )
failed_listeners.append(self._listeners[idx]) failed_listeners.append(self._listeners[idx])

View File

@ -4,7 +4,7 @@ aiodiscover==1.4.8
aiohttp==3.8.1 aiohttp==3.8.1
aiohttp_cors==0.7.0 aiohttp_cors==0.7.0
astral==2.2 astral==2.2
async-upnp-client==0.23.5 async-upnp-client==0.25.0
async_timeout==4.0.2 async_timeout==4.0.2
atomicwrites==1.4.0 atomicwrites==1.4.0
attrs==21.2.0 attrs==21.2.0

View File

@ -327,7 +327,7 @@ asterisk_mbox==0.5.0
# homeassistant.components.ssdp # homeassistant.components.ssdp
# homeassistant.components.upnp # homeassistant.components.upnp
# homeassistant.components.yeelight # homeassistant.components.yeelight
async-upnp-client==0.23.5 async-upnp-client==0.25.0
# homeassistant.components.supla # homeassistant.components.supla
asyncpysupla==0.0.5 asyncpysupla==0.0.5

View File

@ -256,7 +256,7 @@ arcam-fmj==0.12.0
# homeassistant.components.ssdp # homeassistant.components.ssdp
# homeassistant.components.upnp # homeassistant.components.upnp
# homeassistant.components.yeelight # homeassistant.components.yeelight
async-upnp-client==0.23.5 async-upnp-client==0.25.0
# homeassistant.components.sleepiq # homeassistant.components.sleepiq
asyncsleepiq==1.1.0 asyncsleepiq==1.1.0

View File

@ -37,7 +37,10 @@ def aiohttp_notify_servers_mock() -> Iterable[Mock]:
# Every server must be stopped if it was started # Every server must be stopped if it was started
for server in servers: for server in servers:
assert server.start_server.call_count == server.stop_server.call_count assert (
server.async_start_server.call_count
== server.async_stop_server.call_count
)
async def test_get_domain_data(hass: HomeAssistant) -> None: async def test_get_domain_data(hass: HomeAssistant) -> None:
@ -60,7 +63,7 @@ async def test_event_notifier(
# Check that the parameters were passed through to the AiohttpNotifyServer # Check that the parameters were passed through to the AiohttpNotifyServer
aiohttp_notify_servers_mock.assert_called_with( aiohttp_notify_servers_mock.assert_called_with(
requester=ANY, listen_port=0, listen_host=None, callback_url=None, loop=ANY requester=ANY, source=("0.0.0.0", 0), callback_url=None, loop=ANY
) )
# Same address should give same notifier # Same address should give same notifier
@ -79,8 +82,7 @@ async def test_event_notifier(
# Check that the parameters were passed through to the AiohttpNotifyServer # Check that the parameters were passed through to the AiohttpNotifyServer
aiohttp_notify_servers_mock.assert_called_with( aiohttp_notify_servers_mock.assert_called_with(
requester=ANY, requester=ANY,
listen_port=9999, source=("192.88.99.4", 9999),
listen_host="192.88.99.4",
callback_url="http://192.88.99.4:9999/notify", callback_url="http://192.88.99.4:9999/notify",
loop=ANY, loop=ANY,
) )

View File

@ -2,7 +2,7 @@
# pylint: disable=protected-access # pylint: disable=protected-access
from datetime import datetime, timedelta from datetime import datetime, timedelta
from ipaddress import IPv4Address, IPv6Address from ipaddress import IPv4Address
from unittest.mock import ANY, AsyncMock, patch from unittest.mock import ANY, AsyncMock, patch
from async_upnp_client.ssdp import udn_from_headers from async_upnp_client.ssdp import udn_from_headers
@ -25,7 +25,7 @@ from tests.common import async_fire_time_changed
def _ssdp_headers(headers): def _ssdp_headers(headers):
ssdp_headers = CaseInsensitiveDict(headers, _timestamp=datetime(2021, 1, 1, 12, 00)) ssdp_headers = CaseInsensitiveDict(headers, _timestamp=datetime.now())
ssdp_headers["_udn"] = udn_from_headers(ssdp_headers) ssdp_headers["_udn"] = udn_from_headers(ssdp_headers)
return ssdp_headers return ssdp_headers
@ -386,7 +386,7 @@ async def test_discovery_from_advertisement_sets_ssdp_st(
# End compatibility checks # End compatibility checks
@patch( # XXX TODO: Isn't this duplicate with mock_get_source_ip? @patch(
"homeassistant.components.ssdp.Scanner._async_build_source_set", "homeassistant.components.ssdp.Scanner._async_build_source_set",
return_value={IPv4Address("192.168.1.1")}, return_value={IPv4Address("192.168.1.1")},
) )
@ -486,7 +486,7 @@ async def test_scan_with_registered_callback(
mock_call_data.ssdp_usn == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL::mock-st" mock_call_data.ssdp_usn == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL::mock-st"
) )
assert mock_call_data.ssdp_headers["x-rincon-bootseq"] == "55" assert mock_call_data.ssdp_headers["x-rincon-bootseq"] == "55"
assert mock_call_data.ssdp_udn == ANY assert mock_call_data.ssdp_udn == "uuid:TIVRTLSR7ANF-D6E-1557809135086-RETAIL"
assert mock_call_data.ssdp_headers["_timestamp"] == ANY assert mock_call_data.ssdp_headers["_timestamp"] == ANY
assert mock_call_data.x_homeassistant_matching_domains == set() assert mock_call_data.x_homeassistant_matching_domains == set()
assert mock_call_data.upnp == { assert mock_call_data.upnp == {
@ -711,7 +711,7 @@ _ADAPTERS_WITH_MANUAL_CONFIG = [
@patch( @patch(
"homeassistant.components.ssdp.network.async_get_adapters", "homeassistant.components.ssdp.network.async_get_adapters",
return_value=_ADAPTERS_WITH_MANUAL_CONFIG, return_value=_ADAPTERS_WITH_MANUAL_CONFIG,
) # XXX TODO: Isn't this duplicate with mock_get_source_ip? )
async def test_async_detect_interfaces_setting_empty_route( async def test_async_detect_interfaces_setting_empty_route(
mock_get_adapters, mock_get_ssdp, hass mock_get_adapters, mock_get_ssdp, hass
): ):
@ -719,8 +719,8 @@ async def test_async_detect_interfaces_setting_empty_route(
await init_ssdp_component(hass) await init_ssdp_component(hass)
ssdp_listeners = hass.data[ssdp.DOMAIN]._ssdp_listeners ssdp_listeners = hass.data[ssdp.DOMAIN]._ssdp_listeners
source_ips = {ssdp_listener.source_ip for ssdp_listener in ssdp_listeners} sources = {ssdp_listener.source for ssdp_listener in ssdp_listeners}
assert source_ips == {IPv6Address("2001:db8::"), IPv4Address("192.168.1.5")} assert sources == {("2001:db8::%1", 0, 0, 1), ("192.168.1.5", 0)}
@pytest.mark.usefixtures("mock_get_source_ip") @pytest.mark.usefixtures("mock_get_source_ip")
@ -737,14 +737,14 @@ async def test_async_detect_interfaces_setting_empty_route(
@patch( @patch(
"homeassistant.components.ssdp.network.async_get_adapters", "homeassistant.components.ssdp.network.async_get_adapters",
return_value=_ADAPTERS_WITH_MANUAL_CONFIG, return_value=_ADAPTERS_WITH_MANUAL_CONFIG,
) # XXX TODO: Isn't this duplicate with mock_get_source_ip? )
async def test_bind_failure_skips_adapter( async def test_bind_failure_skips_adapter(
mock_get_adapters, mock_get_ssdp, hass, caplog mock_get_adapters, mock_get_ssdp, hass, caplog
): ):
"""Test that an adapter with a bind failure is skipped.""" """Test that an adapter with a bind failure is skipped."""
async def _async_start(self): async def _async_start(self):
if self.source_ip == IPv6Address("2001:db8::"): if self.source == ("2001:db8::%1", 0, 0, 1):
raise OSError raise OSError
SsdpListener.async_start = _async_start SsdpListener.async_start = _async_start
@ -753,10 +753,8 @@ async def test_bind_failure_skips_adapter(
assert "Failed to setup listener for" in caplog.text assert "Failed to setup listener for" in caplog.text
ssdp_listeners = hass.data[ssdp.DOMAIN]._ssdp_listeners ssdp_listeners = hass.data[ssdp.DOMAIN]._ssdp_listeners
source_ips = {ssdp_listener.source_ip for ssdp_listener in ssdp_listeners} sources = {ssdp_listener.source for ssdp_listener in ssdp_listeners}
assert source_ips == { assert sources == {("192.168.1.5", 0)} # Note no SsdpListener for IPv6 address.
IPv4Address("192.168.1.5")
} # Note no SsdpListener for IPv6 address.
@pytest.mark.usefixtures("mock_get_source_ip") @pytest.mark.usefixtures("mock_get_source_ip")
@ -773,7 +771,7 @@ async def test_bind_failure_skips_adapter(
@patch( @patch(
"homeassistant.components.ssdp.network.async_get_adapters", "homeassistant.components.ssdp.network.async_get_adapters",
return_value=_ADAPTERS_WITH_MANUAL_CONFIG, return_value=_ADAPTERS_WITH_MANUAL_CONFIG,
) # XXX TODO: Isn't this duplicate with mock_get_source_ip? )
async def test_ipv4_does_additional_search_for_sonos( async def test_ipv4_does_additional_search_for_sonos(
mock_get_adapters, mock_get_ssdp, hass mock_get_adapters, mock_get_ssdp, hass
): ):

View File

@ -1,7 +1,6 @@
"""Tests for the Yeelight integration.""" """Tests for the Yeelight integration."""
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
from ipaddress import IPv4Address
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from async_upnp_client.search import SsdpSearchListener from async_upnp_client.search import SsdpSearchListener
@ -162,7 +161,7 @@ def _patched_ssdp_listener(info: ssdp.SsdpHeaders, *args, **kwargs):
listener = SsdpSearchListener(*args, **kwargs) listener = SsdpSearchListener(*args, **kwargs)
async def _async_callback(*_): async def _async_callback(*_):
if kwargs["source_ip"] == IPv4Address(FAIL_TO_BIND_IP): if kwargs["source"][0] == FAIL_TO_BIND_IP:
raise OSError raise OSError
await listener.async_connect_callback() await listener.async_connect_callback()

View File

@ -241,7 +241,7 @@ async def test_setup_discovery_with_manually_configured_network_adapter_one_fail
assert hass.states.get(ENTITY_BINARY_SENSOR) is None assert hass.states.get(ENTITY_BINARY_SENSOR) is None
assert hass.states.get(ENTITY_LIGHT) is None assert hass.states.get(ENTITY_LIGHT) is None
assert f"Failed to setup listener for {FAIL_TO_BIND_IP}" in caplog.text assert f"Failed to setup listener for ('{FAIL_TO_BIND_IP}', 0)" in caplog.text
async def test_setup_import(hass: HomeAssistant): async def test_setup_import(hass: HomeAssistant):

View File

@ -955,11 +955,11 @@ async def test_async_detect_interfaces_setting_empty_route_linux(
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_zc.mock_calls[0] == call( assert mock_zc.mock_calls[0] == call(
interfaces=[ interfaces=[
"2001:db8::", "2001:db8::%1",
"fe80::1234:5678:9abc:def0", "fe80::1234:5678:9abc:def0%1",
"192.168.1.5", "192.168.1.5",
"172.16.1.5", "172.16.1.5",
"fe80::dead:beef:dead:beef", "fe80::dead:beef:dead:beef%3",
], ],
ip_version=IPVersion.All, ip_version=IPVersion.All,
) )
@ -1053,7 +1053,7 @@ async def test_async_detect_interfaces_explicitly_set_ipv6_linux(
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_zc.mock_calls[0] == call( assert mock_zc.mock_calls[0] == call(
interfaces=["192.168.1.5", "fe80::dead:beef:dead:beef"], interfaces=["192.168.1.5", "fe80::dead:beef:dead:beef%3"],
ip_version=IPVersion.All, ip_version=IPVersion.All,
) )