mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 10:17:09 +00:00
Reinitialize ssdp discovery flow on unignore (#126557)
This commit is contained in:
parent
4e465a2066
commit
2ee93d974d
@ -12,7 +12,7 @@ from ipaddress import IPv4Address, IPv6Address
|
|||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Any
|
from typing import TYPE_CHECKING, Any
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
@ -47,6 +47,7 @@ from homeassistant.core import Event, HassJob, HomeAssistant, callback as core_c
|
|||||||
from homeassistant.data_entry_flow import BaseServiceInfo
|
from homeassistant.data_entry_flow import BaseServiceInfo
|
||||||
from homeassistant.helpers import config_validation as cv, discovery_flow
|
from homeassistant.helpers import config_validation as cv, discovery_flow
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
from homeassistant.helpers.instance_id import async_get as async_get_instance_id
|
from homeassistant.helpers.instance_id import async_get as async_get_instance_id
|
||||||
from homeassistant.helpers.network import NoURLAvailableError, get_url
|
from homeassistant.helpers.network import NoURLAvailableError, get_url
|
||||||
@ -394,6 +395,12 @@ class Scanner:
|
|||||||
self.hass, self.async_scan, SCAN_INTERVAL, name="SSDP scanner"
|
self.hass, self.async_scan, SCAN_INTERVAL, name="SSDP scanner"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async_dispatcher_connect(
|
||||||
|
self.hass,
|
||||||
|
config_entries.signal_discovered_config_entry_removed(DOMAIN),
|
||||||
|
self._handle_config_entry_removed,
|
||||||
|
)
|
||||||
|
|
||||||
# Trigger the initial-scan.
|
# Trigger the initial-scan.
|
||||||
await self.async_scan()
|
await self.async_scan()
|
||||||
|
|
||||||
@ -502,6 +509,7 @@ class Scanner:
|
|||||||
dst: DeviceOrServiceType,
|
dst: DeviceOrServiceType,
|
||||||
source: SsdpSource,
|
source: SsdpSource,
|
||||||
info_desc: Mapping[str, Any],
|
info_desc: Mapping[str, Any],
|
||||||
|
skip_callbacks: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle a device/service change."""
|
"""Handle a device/service change."""
|
||||||
matching_domains: set[str] = set()
|
matching_domains: set[str] = set()
|
||||||
@ -526,7 +534,7 @@ class Scanner:
|
|||||||
)
|
)
|
||||||
discovery_info.x_homeassistant_matching_domains = matching_domains
|
discovery_info.x_homeassistant_matching_domains = matching_domains
|
||||||
|
|
||||||
if callbacks:
|
if callbacks and not skip_callbacks:
|
||||||
ssdp_change = SSDP_SOURCE_SSDP_CHANGE_MAPPING[source]
|
ssdp_change = SSDP_SOURCE_SSDP_CHANGE_MAPPING[source]
|
||||||
_async_process_callbacks(self.hass, callbacks, discovery_info, ssdp_change)
|
_async_process_callbacks(self.hass, callbacks, discovery_info, ssdp_change)
|
||||||
|
|
||||||
@ -537,14 +545,20 @@ class Scanner:
|
|||||||
|
|
||||||
_LOGGER.debug("Discovery info: %s", discovery_info)
|
_LOGGER.debug("Discovery info: %s", discovery_info)
|
||||||
|
|
||||||
location = ssdp_device.location
|
if not matching_domains:
|
||||||
|
return # avoid creating DiscoveryKey if there are no matches
|
||||||
|
|
||||||
|
discovery_key = discovery_flow.DiscoveryKey(
|
||||||
|
domain=DOMAIN, key=ssdp_device.udn, version=1
|
||||||
|
)
|
||||||
for domain in matching_domains:
|
for domain in matching_domains:
|
||||||
_LOGGER.debug("Discovered %s at %s", domain, location)
|
_LOGGER.debug("Discovered %s at %s", domain, ssdp_device.location)
|
||||||
discovery_flow.async_create_flow(
|
discovery_flow.async_create_flow(
|
||||||
self.hass,
|
self.hass,
|
||||||
domain,
|
domain,
|
||||||
{"source": config_entries.SOURCE_SSDP},
|
{"source": config_entries.SOURCE_SSDP},
|
||||||
discovery_info,
|
discovery_info,
|
||||||
|
discovery_key=discovery_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _async_dismiss_discoveries(
|
def _async_dismiss_discoveries(
|
||||||
@ -565,14 +579,13 @@ class Scanner:
|
|||||||
) -> Mapping[str, str]:
|
) -> Mapping[str, str]:
|
||||||
"""Get description dict."""
|
"""Get description dict."""
|
||||||
assert self._description_cache is not None
|
assert self._description_cache is not None
|
||||||
|
cache = self._description_cache
|
||||||
|
|
||||||
has_description, description = self._description_cache.peek_description_dict(
|
has_description, description = cache.peek_description_dict(location)
|
||||||
location
|
|
||||||
)
|
|
||||||
if has_description:
|
if has_description:
|
||||||
return description or {}
|
return description or {}
|
||||||
|
|
||||||
return await self._description_cache.async_get_description_dict(location) or {}
|
return await cache.async_get_description_dict(location) or {}
|
||||||
|
|
||||||
async def _async_headers_to_discovery_info(
|
async def _async_headers_to_discovery_info(
|
||||||
self, ssdp_device: SsdpDevice, headers: CaseInsensitiveDict
|
self, ssdp_device: SsdpDevice, headers: CaseInsensitiveDict
|
||||||
@ -581,8 +594,6 @@ class Scanner:
|
|||||||
|
|
||||||
Building this is a bit expensive so we only do it on demand.
|
Building this is a bit expensive so we only do it on demand.
|
||||||
"""
|
"""
|
||||||
assert self._description_cache is not None
|
|
||||||
|
|
||||||
location = headers["location"]
|
location = headers["location"]
|
||||||
info_desc = await self._async_get_description_dict(location)
|
info_desc = await self._async_get_description_dict(location)
|
||||||
return discovery_info_from_headers_and_description(
|
return discovery_info_from_headers_and_description(
|
||||||
@ -618,6 +629,37 @@ class Scanner:
|
|||||||
if ssdp_device.udn == udn
|
if ssdp_device.udn == udn
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@core_callback
|
||||||
|
def _handle_config_entry_removed(
|
||||||
|
self,
|
||||||
|
entry: config_entries.ConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Handle config entry changes."""
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert self._description_cache is not None
|
||||||
|
cache = self._description_cache
|
||||||
|
for discovery_key in entry.discovery_keys[DOMAIN]:
|
||||||
|
if discovery_key.version != 1 or not isinstance(discovery_key.key, str):
|
||||||
|
continue
|
||||||
|
udn = discovery_key.key
|
||||||
|
_LOGGER.debug("Rediscover service %s", udn)
|
||||||
|
|
||||||
|
for ssdp_device in self._ssdp_devices:
|
||||||
|
if ssdp_device.udn != udn:
|
||||||
|
continue
|
||||||
|
for dst in ssdp_device.all_combined_headers:
|
||||||
|
has_cached_desc, info_desc = cache.peek_description_dict(
|
||||||
|
ssdp_device.location
|
||||||
|
)
|
||||||
|
if has_cached_desc and info_desc:
|
||||||
|
self._ssdp_listener_process_callback(
|
||||||
|
ssdp_device,
|
||||||
|
dst,
|
||||||
|
SsdpSource.SEARCH,
|
||||||
|
info_desc,
|
||||||
|
True, # Skip integration callbacks
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def discovery_info_from_headers_and_description(
|
def discovery_info_from_headers_and_description(
|
||||||
ssdp_device: SsdpDevice,
|
ssdp_device: SsdpDevice,
|
||||||
|
@ -18,10 +18,16 @@ from homeassistant.const import (
|
|||||||
MATCH_ALL,
|
MATCH_ALL,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.discovery_flow import DiscoveryKey
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import (
|
||||||
|
MockConfigEntry,
|
||||||
|
MockModule,
|
||||||
|
async_fire_time_changed,
|
||||||
|
mock_integration,
|
||||||
|
)
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
|
||||||
@ -65,7 +71,8 @@ async def test_ssdp_flow_dispatched_on_st(
|
|||||||
assert len(mock_flow_init.mock_calls) == 1
|
assert len(mock_flow_init.mock_calls) == 1
|
||||||
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
||||||
assert mock_flow_init.mock_calls[0][2]["context"] == {
|
assert mock_flow_init.mock_calls[0][2]["context"] == {
|
||||||
"source": config_entries.SOURCE_SSDP
|
"discovery_key": DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
}
|
}
|
||||||
mock_call_data: ssdp.SsdpServiceInfo = mock_flow_init.mock_calls[0][2]["data"]
|
mock_call_data: ssdp.SsdpServiceInfo = mock_flow_init.mock_calls[0][2]["data"]
|
||||||
assert mock_call_data.ssdp_st == "mock-st"
|
assert mock_call_data.ssdp_st == "mock-st"
|
||||||
@ -108,7 +115,8 @@ async def test_ssdp_flow_dispatched_on_manufacturer_url(
|
|||||||
assert len(mock_flow_init.mock_calls) == 1
|
assert len(mock_flow_init.mock_calls) == 1
|
||||||
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
||||||
assert mock_flow_init.mock_calls[0][2]["context"] == {
|
assert mock_flow_init.mock_calls[0][2]["context"] == {
|
||||||
"source": config_entries.SOURCE_SSDP
|
"discovery_key": DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
}
|
}
|
||||||
mock_call_data: ssdp.SsdpServiceInfo = mock_flow_init.mock_calls[0][2]["data"]
|
mock_call_data: ssdp.SsdpServiceInfo = mock_flow_init.mock_calls[0][2]["data"]
|
||||||
assert mock_call_data.ssdp_st == "mock-st"
|
assert mock_call_data.ssdp_st == "mock-st"
|
||||||
@ -163,7 +171,8 @@ async def test_scan_match_upnp_devicedesc_manufacturer(
|
|||||||
assert len(mock_flow_init.mock_calls) == 1
|
assert len(mock_flow_init.mock_calls) == 1
|
||||||
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
||||||
assert mock_flow_init.mock_calls[0][2]["context"] == {
|
assert mock_flow_init.mock_calls[0][2]["context"] == {
|
||||||
"source": config_entries.SOURCE_SSDP
|
"discovery_key": DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -208,7 +217,8 @@ async def test_scan_match_upnp_devicedesc_devicetype(
|
|||||||
assert len(mock_flow_init.mock_calls) == 1
|
assert len(mock_flow_init.mock_calls) == 1
|
||||||
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
||||||
assert mock_flow_init.mock_calls[0][2]["context"] == {
|
assert mock_flow_init.mock_calls[0][2]["context"] == {
|
||||||
"source": config_entries.SOURCE_SSDP
|
"discovery_key": DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -339,7 +349,14 @@ async def test_flow_start_only_alive(
|
|||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
mock_flow_init.assert_awaited_once_with(
|
mock_flow_init.assert_awaited_once_with(
|
||||||
"mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY
|
"mock-domain",
|
||||||
|
context={
|
||||||
|
"discovery_key": DiscoveryKey(
|
||||||
|
domain="ssdp", key="uuid:mock-udn", version=1
|
||||||
|
),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
|
},
|
||||||
|
data=ANY,
|
||||||
)
|
)
|
||||||
|
|
||||||
# ssdp:alive advertisement should start a flow
|
# ssdp:alive advertisement should start a flow
|
||||||
@ -356,7 +373,14 @@ async def test_flow_start_only_alive(
|
|||||||
ssdp_listener._on_alive(mock_ssdp_advertisement)
|
ssdp_listener._on_alive(mock_ssdp_advertisement)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_flow_init.assert_awaited_once_with(
|
mock_flow_init.assert_awaited_once_with(
|
||||||
"mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY
|
"mock-domain",
|
||||||
|
context={
|
||||||
|
"discovery_key": DiscoveryKey(
|
||||||
|
domain="ssdp", key="uuid:mock-udn", version=1
|
||||||
|
),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
|
},
|
||||||
|
data=ANY,
|
||||||
)
|
)
|
||||||
|
|
||||||
# ssdp:byebye advertisement should not start a flow
|
# ssdp:byebye advertisement should not start a flow
|
||||||
@ -372,7 +396,14 @@ async def test_flow_start_only_alive(
|
|||||||
ssdp_listener._on_update(mock_ssdp_advertisement)
|
ssdp_listener._on_update(mock_ssdp_advertisement)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_flow_init.assert_awaited_once_with(
|
mock_flow_init.assert_awaited_once_with(
|
||||||
"mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY
|
"mock-domain",
|
||||||
|
context={
|
||||||
|
"discovery_key": DiscoveryKey(
|
||||||
|
domain="ssdp", key="uuid:mock-udn", version=1
|
||||||
|
),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
|
},
|
||||||
|
data=ANY,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -824,7 +855,14 @@ async def test_flow_dismiss_on_byebye(
|
|||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
mock_flow_init.assert_awaited_once_with(
|
mock_flow_init.assert_awaited_once_with(
|
||||||
"mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY
|
"mock-domain",
|
||||||
|
context={
|
||||||
|
"discovery_key": DiscoveryKey(
|
||||||
|
domain="ssdp", key="uuid:mock-udn", version=1
|
||||||
|
),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
|
},
|
||||||
|
data=ANY,
|
||||||
)
|
)
|
||||||
|
|
||||||
# ssdp:alive advertisement should start a flow
|
# ssdp:alive advertisement should start a flow
|
||||||
@ -841,7 +879,14 @@ async def test_flow_dismiss_on_byebye(
|
|||||||
ssdp_listener._on_alive(mock_ssdp_advertisement)
|
ssdp_listener._on_alive(mock_ssdp_advertisement)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
mock_flow_init.assert_awaited_once_with(
|
mock_flow_init.assert_awaited_once_with(
|
||||||
"mock-domain", context={"source": config_entries.SOURCE_SSDP}, data=ANY
|
"mock-domain",
|
||||||
|
context={
|
||||||
|
"discovery_key": DiscoveryKey(
|
||||||
|
domain="ssdp", key="uuid:mock-udn", version=1
|
||||||
|
),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
|
},
|
||||||
|
data=ANY,
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_ssdp_advertisement["nts"] = "ssdp:byebye"
|
mock_ssdp_advertisement["nts"] = "ssdp:byebye"
|
||||||
@ -859,3 +904,298 @@ async def test_flow_dismiss_on_byebye(
|
|||||||
|
|
||||||
assert len(mock_async_progress_by_init_data_type.mock_calls) == 1
|
assert len(mock_async_progress_by_init_data_type.mock_calls) == 1
|
||||||
assert mock_async_abort.mock_calls[0][1][0] == "mock_flow_id"
|
assert mock_async_abort.mock_calls[0][1][0] == "mock_flow_id"
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"homeassistant.components.ssdp.async_get_ssdp",
|
||||||
|
return_value={"mock-domain": [{"st": "mock-st"}]},
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"entry_domain",
|
||||||
|
"entry_discovery_keys",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
# Matching discovery key
|
||||||
|
(
|
||||||
|
"mock-domain",
|
||||||
|
{"ssdp": (DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),)},
|
||||||
|
),
|
||||||
|
# Matching discovery key
|
||||||
|
(
|
||||||
|
"mock-domain",
|
||||||
|
{
|
||||||
|
"ssdp": (DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),),
|
||||||
|
"other": (DiscoveryKey(domain="other", key="blah", version=1),),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# Matching discovery key, other domain
|
||||||
|
# Note: Rediscovery is not currently restricted to the domain of the removed
|
||||||
|
# entry. Such a check can be added if needed.
|
||||||
|
(
|
||||||
|
"comp",
|
||||||
|
{"ssdp": (DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),)},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("entry_source", [config_entries.SOURCE_IGNORE])
|
||||||
|
async def test_ssdp_rediscover(
|
||||||
|
mock_get_ssdp,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
mock_flow_init,
|
||||||
|
entry_domain: str,
|
||||||
|
entry_discovery_keys: tuple,
|
||||||
|
entry_source: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test we reinitiate flows when an ignored config entry is removed."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=entry_domain,
|
||||||
|
discovery_keys=entry_discovery_keys,
|
||||||
|
unique_id="mock-unique-id",
|
||||||
|
state=config_entries.ConfigEntryState.LOADED,
|
||||||
|
source=entry_source,
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
mock_ssdp_search_response = _ssdp_headers(
|
||||||
|
{
|
||||||
|
"st": "mock-st",
|
||||||
|
"location": "http://1.1.1.1",
|
||||||
|
"usn": "uuid:mock-udn::mock-st",
|
||||||
|
"server": "mock-server",
|
||||||
|
"ext": "",
|
||||||
|
"_source": "search",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://1.1.1.1",
|
||||||
|
text="""
|
||||||
|
<root>
|
||||||
|
<device>
|
||||||
|
<deviceType>Paulus</deviceType>
|
||||||
|
<manufacturer>Paulus</manufacturer>
|
||||||
|
</device>
|
||||||
|
</root>
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
ssdp_listener = await init_ssdp_component(hass)
|
||||||
|
ssdp_listener._on_search(mock_ssdp_search_response)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
expected_context = {
|
||||||
|
"discovery_key": DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
|
}
|
||||||
|
assert len(mock_flow_init.mock_calls) == 1
|
||||||
|
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
||||||
|
assert mock_flow_init.mock_calls[0][2]["context"] == expected_context
|
||||||
|
mock_call_data: ssdp.SsdpServiceInfo = mock_flow_init.mock_calls[0][2]["data"]
|
||||||
|
assert mock_call_data.ssdp_st == "mock-st"
|
||||||
|
assert mock_call_data.ssdp_location == "http://1.1.1.1"
|
||||||
|
|
||||||
|
await hass.config_entries.async_remove(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_flow_init.mock_calls) == 3
|
||||||
|
assert mock_flow_init.mock_calls[1][1][0] == entry_domain
|
||||||
|
assert mock_flow_init.mock_calls[1][2]["context"] == {"source": "unignore"}
|
||||||
|
assert mock_flow_init.mock_calls[2][1][0] == "mock-domain"
|
||||||
|
assert mock_flow_init.mock_calls[2][2]["context"] == expected_context
|
||||||
|
assert (
|
||||||
|
mock_flow_init.mock_calls[2][2]["data"]
|
||||||
|
== mock_flow_init.mock_calls[0][2]["data"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"homeassistant.components.ssdp.async_get_ssdp",
|
||||||
|
return_value={"mock-domain": [{"st": "mock-st"}]},
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"entry_domain",
|
||||||
|
"entry_discovery_keys",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
# Matching discovery key
|
||||||
|
(
|
||||||
|
"mock-domain",
|
||||||
|
{"ssdp": (DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),)},
|
||||||
|
),
|
||||||
|
# Matching discovery key
|
||||||
|
(
|
||||||
|
"mock-domain",
|
||||||
|
{
|
||||||
|
"ssdp": (DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),),
|
||||||
|
"other": (DiscoveryKey(domain="other", key="blah", version=1),),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# Matching discovery key, other domain
|
||||||
|
# Note: Rediscovery is not currently restricted to the domain of the removed
|
||||||
|
# entry. Such a check can be added if needed.
|
||||||
|
(
|
||||||
|
"comp",
|
||||||
|
{"ssdp": (DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),)},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"entry_source", [config_entries.SOURCE_USER, config_entries.SOURCE_ZEROCONF]
|
||||||
|
)
|
||||||
|
async def test_ssdp_rediscover_2(
|
||||||
|
mock_get_ssdp,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
mock_flow_init,
|
||||||
|
entry_domain: str,
|
||||||
|
entry_discovery_keys: tuple,
|
||||||
|
entry_source: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test we reinitiate flows when an ignored config entry is removed.
|
||||||
|
|
||||||
|
This test can be merged with test_zeroconf_rediscover when
|
||||||
|
async_step_unignore has been removed from the ConfigFlow base class.
|
||||||
|
"""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=entry_domain,
|
||||||
|
discovery_keys=entry_discovery_keys,
|
||||||
|
unique_id="mock-unique-id",
|
||||||
|
state=config_entries.ConfigEntryState.LOADED,
|
||||||
|
source=entry_source,
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
mock_ssdp_search_response = _ssdp_headers(
|
||||||
|
{
|
||||||
|
"st": "mock-st",
|
||||||
|
"location": "http://1.1.1.1",
|
||||||
|
"usn": "uuid:mock-udn::mock-st",
|
||||||
|
"server": "mock-server",
|
||||||
|
"ext": "",
|
||||||
|
"_source": "search",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://1.1.1.1",
|
||||||
|
text="""
|
||||||
|
<root>
|
||||||
|
<device>
|
||||||
|
<deviceType>Paulus</deviceType>
|
||||||
|
<manufacturer>Paulus</manufacturer>
|
||||||
|
</device>
|
||||||
|
</root>
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
ssdp_listener = await init_ssdp_component(hass)
|
||||||
|
ssdp_listener._on_search(mock_ssdp_search_response)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
expected_context = {
|
||||||
|
"discovery_key": DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
|
}
|
||||||
|
assert len(mock_flow_init.mock_calls) == 1
|
||||||
|
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
||||||
|
assert mock_flow_init.mock_calls[0][2]["context"] == expected_context
|
||||||
|
mock_call_data: ssdp.SsdpServiceInfo = mock_flow_init.mock_calls[0][2]["data"]
|
||||||
|
assert mock_call_data.ssdp_st == "mock-st"
|
||||||
|
assert mock_call_data.ssdp_location == "http://1.1.1.1"
|
||||||
|
|
||||||
|
await hass.config_entries.async_remove(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_flow_init.mock_calls) == 2
|
||||||
|
assert mock_flow_init.mock_calls[1][1][0] == "mock-domain"
|
||||||
|
assert mock_flow_init.mock_calls[1][2]["context"] == expected_context
|
||||||
|
assert (
|
||||||
|
mock_flow_init.mock_calls[1][2]["data"]
|
||||||
|
== mock_flow_init.mock_calls[0][2]["data"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"homeassistant.components.ssdp.async_get_ssdp",
|
||||||
|
return_value={"mock-domain": [{"st": "mock-st"}]},
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"entry_domain",
|
||||||
|
"entry_discovery_keys",
|
||||||
|
"entry_source",
|
||||||
|
"entry_unique_id",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
# Discovery key from other domain
|
||||||
|
(
|
||||||
|
"mock-domain",
|
||||||
|
{"dhcp": (DiscoveryKey(domain="dhcp", key="uuid:mock-udn", version=1),)},
|
||||||
|
config_entries.SOURCE_IGNORE,
|
||||||
|
"mock-unique-id",
|
||||||
|
),
|
||||||
|
# Discovery key from the future
|
||||||
|
(
|
||||||
|
"mock-domain",
|
||||||
|
{"ssdp": (DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=2),)},
|
||||||
|
config_entries.SOURCE_IGNORE,
|
||||||
|
"mock-unique-id",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_ssdp_rediscover_no_match(
|
||||||
|
mock_get_ssdp,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_flow_init,
|
||||||
|
entry_domain: str,
|
||||||
|
entry_discovery_keys: tuple,
|
||||||
|
entry_source: str,
|
||||||
|
entry_unique_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test we don't reinitiate flows when a non matching config entry is removed."""
|
||||||
|
mock_integration(hass, MockModule(entry_domain))
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=entry_domain,
|
||||||
|
discovery_keys=entry_discovery_keys,
|
||||||
|
unique_id=entry_unique_id,
|
||||||
|
state=config_entries.ConfigEntryState.LOADED,
|
||||||
|
source=entry_source,
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
mock_ssdp_search_response = _ssdp_headers(
|
||||||
|
{
|
||||||
|
"st": "mock-st",
|
||||||
|
"location": "http://1.1.1.1",
|
||||||
|
"usn": "uuid:mock-udn::mock-st",
|
||||||
|
"server": "mock-server",
|
||||||
|
"ext": "",
|
||||||
|
"_source": "search",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ssdp_listener = await init_ssdp_component(hass)
|
||||||
|
ssdp_listener._on_search(mock_ssdp_search_response)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
expected_context = {
|
||||||
|
"discovery_key": DiscoveryKey(domain="ssdp", key="uuid:mock-udn", version=1),
|
||||||
|
"source": config_entries.SOURCE_SSDP,
|
||||||
|
}
|
||||||
|
assert len(mock_flow_init.mock_calls) == 1
|
||||||
|
assert mock_flow_init.mock_calls[0][1][0] == "mock-domain"
|
||||||
|
assert mock_flow_init.mock_calls[0][2]["context"] == expected_context
|
||||||
|
mock_call_data: ssdp.SsdpServiceInfo = mock_flow_init.mock_calls[0][2]["data"]
|
||||||
|
assert mock_call_data.ssdp_st == "mock-st"
|
||||||
|
assert mock_call_data.ssdp_location == "http://1.1.1.1"
|
||||||
|
|
||||||
|
await hass.config_entries.async_remove(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(mock_flow_init.mock_calls) == 2
|
||||||
|
assert mock_flow_init.mock_calls[1][1][0] == entry_domain
|
||||||
|
assert mock_flow_init.mock_calls[1][2]["context"] == {"source": "unignore"}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user