mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Reinitialize zeroconf discovery flow on config entry removal (#126595)
This commit is contained in:
parent
004941cc57
commit
589910b49b
@ -398,14 +398,12 @@ class ZeroconfDiscovery:
|
||||
entry: config_entries.ConfigEntry,
|
||||
) -> None:
|
||||
"""Handle config entry changes."""
|
||||
if entry.source != config_entries.SOURCE_IGNORE:
|
||||
return
|
||||
for discovery_key in entry.discovery_keys[DOMAIN]:
|
||||
if discovery_key.version != 1:
|
||||
continue
|
||||
_type = discovery_key.key[0]
|
||||
name = discovery_key.key[1]
|
||||
_LOGGER.debug("Rediscover unignored service %s.%s", _type, name)
|
||||
_LOGGER.debug("Rediscover service %s.%s", _type, name)
|
||||
self._async_service_update(self.zeroconf, _type, name)
|
||||
|
||||
def _async_dismiss_discoveries(self, name: str) -> None:
|
||||
|
@ -1388,7 +1388,6 @@ class ConfigEntriesFlowManager(data_entry_flow.FlowManager[ConfigFlowResult]):
|
||||
result["handler"], unique_id
|
||||
)
|
||||
)
|
||||
and entry.source == SOURCE_IGNORE
|
||||
and discovery_key
|
||||
not in (
|
||||
known_discovery_keys := entry.discovery_keys.get(
|
||||
|
@ -1456,10 +1456,12 @@ async def test_zeroconf_removed(hass: HomeAssistant) -> None:
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("entry_source", [config_entries.SOURCE_IGNORE])
|
||||
async def test_zeroconf_rediscover(
|
||||
hass: HomeAssistant,
|
||||
entry_domain: str,
|
||||
entry_discovery_keys: tuple,
|
||||
entry_source: str,
|
||||
) -> None:
|
||||
"""Test we reinitiate flows when an ignored config entry is removed."""
|
||||
|
||||
@ -1477,7 +1479,7 @@ async def test_zeroconf_rediscover(
|
||||
discovery_keys=entry_discovery_keys,
|
||||
unique_id="mock-unique-id",
|
||||
state=config_entries.ConfigEntryState.LOADED,
|
||||
source=config_entries.SOURCE_IGNORE,
|
||||
source=entry_source,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
@ -1534,6 +1536,145 @@ async def test_zeroconf_rediscover(
|
||||
assert mock_config_flow.mock_calls[2][2]["context"] == expected_context
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_async_zeroconf")
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"entry_domain",
|
||||
"entry_discovery_keys",
|
||||
),
|
||||
[
|
||||
# Matching discovery key
|
||||
(
|
||||
"shelly",
|
||||
{
|
||||
"zeroconf": (
|
||||
DiscoveryKey(
|
||||
domain="zeroconf",
|
||||
key=("_http._tcp.local.", "Shelly108._http._tcp.local."),
|
||||
version=1,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
# Matching discovery key
|
||||
(
|
||||
"shelly",
|
||||
{
|
||||
"zeroconf": (
|
||||
DiscoveryKey(
|
||||
domain="zeroconf",
|
||||
key=("_http._tcp.local.", "Shelly108._http._tcp.local."),
|
||||
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",
|
||||
{
|
||||
"zeroconf": (
|
||||
DiscoveryKey(
|
||||
domain="zeroconf",
|
||||
key=("_http._tcp.local.", "Shelly108._http._tcp.local."),
|
||||
version=1,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"entry_source", [config_entries.SOURCE_USER, config_entries.SOURCE_ZEROCONF]
|
||||
)
|
||||
async def test_zeroconf_rediscover_2(
|
||||
hass: HomeAssistant,
|
||||
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.
|
||||
"""
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
with (
|
||||
patch.dict(
|
||||
zc_gen.ZEROCONF,
|
||||
{
|
||||
"_http._tcp.local.": [
|
||||
{
|
||||
"domain": "shelly",
|
||||
"name": "shelly*",
|
||||
"properties": {"macaddress": "ffaadd*"},
|
||||
}
|
||||
]
|
||||
},
|
||||
clear=True,
|
||||
),
|
||||
patch.object(hass.config_entries.flow, "async_init") as mock_config_flow,
|
||||
patch.object(
|
||||
zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock
|
||||
) as mock_service_browser,
|
||||
patch(
|
||||
"homeassistant.components.zeroconf.AsyncServiceInfo",
|
||||
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()
|
||||
|
||||
expected_context = {
|
||||
"discovery_key": DiscoveryKey(
|
||||
domain="zeroconf",
|
||||
key=("_http._tcp.local.", "Shelly108._http._tcp.local."),
|
||||
version=1,
|
||||
),
|
||||
"source": "zeroconf",
|
||||
}
|
||||
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"
|
||||
assert mock_config_flow.mock_calls[0][2]["context"] == expected_context
|
||||
|
||||
await hass.config_entries.async_remove(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_service_browser.mock_calls) == 1
|
||||
assert len(mock_config_flow.mock_calls) == 2
|
||||
assert mock_config_flow.mock_calls[1][1][0] == "shelly"
|
||||
assert mock_config_flow.mock_calls[1][2]["context"] == expected_context
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_async_zeroconf")
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
@ -1654,110 +1795,3 @@ async def test_zeroconf_rediscover_no_match(
|
||||
assert mock_config_flow.mock_calls[1][2]["context"] == {
|
||||
"source": "unignore",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_async_zeroconf")
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"entry_domain",
|
||||
"entry_discovery_keys",
|
||||
"entry_source",
|
||||
"entry_unique_id",
|
||||
),
|
||||
[
|
||||
# Source not SOURCE_IGNORE
|
||||
(
|
||||
"shelly",
|
||||
{
|
||||
"zeroconf": (
|
||||
DiscoveryKey(
|
||||
domain="zeroconf",
|
||||
key=("_http._tcp.local.", "Shelly108._http._tcp.local."),
|
||||
version=1,
|
||||
),
|
||||
)
|
||||
},
|
||||
config_entries.SOURCE_ZEROCONF,
|
||||
"mock-unique-id",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_zeroconf_rediscover_no_match_2(
|
||||
hass: HomeAssistant,
|
||||
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.
|
||||
|
||||
This test can be merged with test_zeroconf_rediscover_no_match when
|
||||
async_step_unignore has been removed from the ConfigFlow base class.
|
||||
"""
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
hass.config.components.add(entry_domain)
|
||||
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)
|
||||
|
||||
with (
|
||||
patch.dict(
|
||||
zc_gen.ZEROCONF,
|
||||
{
|
||||
"_http._tcp.local.": [
|
||||
{
|
||||
"domain": "shelly",
|
||||
"name": "shelly*",
|
||||
"properties": {"macaddress": "ffaadd*"},
|
||||
}
|
||||
]
|
||||
},
|
||||
clear=True,
|
||||
),
|
||||
patch.object(hass.config_entries.flow, "async_init") as mock_config_flow,
|
||||
patch.object(
|
||||
zeroconf, "AsyncServiceBrowser", side_effect=http_only_service_update_mock
|
||||
) as mock_service_browser,
|
||||
patch(
|
||||
"homeassistant.components.zeroconf.AsyncServiceInfo",
|
||||
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()
|
||||
|
||||
expected_context = {
|
||||
"discovery_key": DiscoveryKey(
|
||||
domain="zeroconf",
|
||||
key=("_http._tcp.local.", "Shelly108._http._tcp.local."),
|
||||
version=1,
|
||||
),
|
||||
"source": "zeroconf",
|
||||
}
|
||||
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"
|
||||
assert mock_config_flow.mock_calls[0][2]["context"] == expected_context
|
||||
|
||||
await hass.config_entries.async_remove(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_service_browser.mock_calls) == 1
|
||||
assert len(mock_config_flow.mock_calls) == 1
|
||||
|
@ -2911,7 +2911,6 @@ async def test_manual_add_overrides_ignored_entry_singleton(
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"discovery_keys",
|
||||
"entry_source",
|
||||
"entry_unique_id",
|
||||
"flow_context",
|
||||
"flow_source",
|
||||
@ -2922,7 +2921,6 @@ async def test_manual_add_overrides_ignored_entry_singleton(
|
||||
# No discovery key
|
||||
(
|
||||
{},
|
||||
config_entries.SOURCE_IGNORE,
|
||||
"mock-unique-id",
|
||||
{},
|
||||
config_entries.SOURCE_ZEROCONF,
|
||||
@ -2932,7 +2930,6 @@ async def test_manual_add_overrides_ignored_entry_singleton(
|
||||
# Discovery key added to ignored entry data
|
||||
(
|
||||
{},
|
||||
config_entries.SOURCE_IGNORE,
|
||||
"mock-unique-id",
|
||||
{"discovery_key": DiscoveryKey(domain="test", key="blah", version=1)},
|
||||
config_entries.SOURCE_ZEROCONF,
|
||||
@ -2942,7 +2939,6 @@ async def test_manual_add_overrides_ignored_entry_singleton(
|
||||
# Discovery key added to ignored entry data
|
||||
(
|
||||
{"test": (DiscoveryKey(domain="test", key="bleh", version=1),)},
|
||||
config_entries.SOURCE_IGNORE,
|
||||
"mock-unique-id",
|
||||
{"discovery_key": DiscoveryKey(domain="test", key="blah", version=1)},
|
||||
config_entries.SOURCE_ZEROCONF,
|
||||
@ -2970,7 +2966,6 @@ async def test_manual_add_overrides_ignored_entry_singleton(
|
||||
DiscoveryKey(domain="test", key="10", version=1),
|
||||
)
|
||||
},
|
||||
config_entries.SOURCE_IGNORE,
|
||||
"mock-unique-id",
|
||||
{"discovery_key": DiscoveryKey(domain="test", key="11", version=1)},
|
||||
config_entries.SOURCE_ZEROCONF,
|
||||
@ -2993,33 +2988,102 @@ async def test_manual_add_overrides_ignored_entry_singleton(
|
||||
# Discovery key already in ignored entry data
|
||||
(
|
||||
{"test": (DiscoveryKey(domain="test", key="blah", version=1),)},
|
||||
config_entries.SOURCE_IGNORE,
|
||||
"mock-unique-id",
|
||||
{"discovery_key": DiscoveryKey(domain="test", key="blah", version=1)},
|
||||
config_entries.SOURCE_ZEROCONF,
|
||||
data_entry_flow.FlowResultType.ABORT,
|
||||
{"test": (DiscoveryKey(domain="test", key="blah", version=1),)},
|
||||
),
|
||||
# Discovery key not added to user entry data
|
||||
(
|
||||
{},
|
||||
config_entries.SOURCE_USER,
|
||||
"mock-unique-id",
|
||||
{"discovery_key": DiscoveryKey(domain="test", key="blah", version=1)},
|
||||
config_entries.SOURCE_ZEROCONF,
|
||||
data_entry_flow.FlowResultType.ABORT,
|
||||
{},
|
||||
),
|
||||
# Flow not aborted when unique id is not matching
|
||||
(
|
||||
{},
|
||||
config_entries.SOURCE_IGNORE,
|
||||
"mock-unique-id-2",
|
||||
{"discovery_key": DiscoveryKey(domain="test", key="blah", version=1)},
|
||||
config_entries.SOURCE_ZEROCONF,
|
||||
data_entry_flow.FlowResultType.FORM,
|
||||
{},
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"entry_source",
|
||||
[
|
||||
config_entries.SOURCE_IGNORE,
|
||||
config_entries.SOURCE_USER,
|
||||
config_entries.SOURCE_ZEROCONF,
|
||||
],
|
||||
)
|
||||
async def test_update_discovery_keys(
|
||||
hass: HomeAssistant,
|
||||
manager: config_entries.ConfigEntries,
|
||||
discovery_keys: tuple,
|
||||
entry_source: str,
|
||||
entry_unique_id: str,
|
||||
flow_context: dict,
|
||||
flow_source: str,
|
||||
flow_result: data_entry_flow.FlowResultType,
|
||||
updated_discovery_keys: tuple,
|
||||
) -> None:
|
||||
"""Test that discovery keys of an entry can be updated."""
|
||||
hass.config.components.add("comp")
|
||||
entry = MockConfigEntry(
|
||||
domain="comp",
|
||||
discovery_keys=discovery_keys,
|
||||
unique_id=entry_unique_id,
|
||||
state=config_entries.ConfigEntryState.LOADED,
|
||||
source=entry_source,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
mock_integration(hass, MockModule("comp"))
|
||||
mock_platform(hass, "comp.config_flow", None)
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Test user step."""
|
||||
await self.async_set_unique_id("mock-unique-id")
|
||||
self._abort_if_unique_id_configured(reload_on_update=False)
|
||||
return self.async_show_form(step_id="step2")
|
||||
|
||||
async def async_step_step2(self, user_input=None):
|
||||
raise NotImplementedError
|
||||
|
||||
async def async_step_zeroconf(self, discovery_info=None):
|
||||
"""Test zeroconf step."""
|
||||
return await self.async_step_user(discovery_info)
|
||||
|
||||
with (
|
||||
mock_config_flow("comp", TestFlow),
|
||||
patch(
|
||||
"homeassistant.config_entries.ConfigEntries.async_reload"
|
||||
) as async_reload,
|
||||
):
|
||||
result = await manager.flow.async_init(
|
||||
"comp", context={"source": flow_source} | flow_context
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == flow_result
|
||||
assert entry.data == {}
|
||||
assert entry.discovery_keys == updated_discovery_keys
|
||||
assert len(async_reload.mock_calls) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"discovery_keys",
|
||||
"entry_source",
|
||||
"entry_unique_id",
|
||||
"flow_context",
|
||||
"flow_source",
|
||||
"flow_result",
|
||||
"updated_discovery_keys",
|
||||
),
|
||||
[
|
||||
# Flow not aborted when user initiated flow
|
||||
(
|
||||
{},
|
||||
@ -3032,7 +3096,7 @@ async def test_manual_add_overrides_ignored_entry_singleton(
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_ignored_entry_update_discovery_keys(
|
||||
async def test_update_discovery_keys_2(
|
||||
hass: HomeAssistant,
|
||||
manager: config_entries.ConfigEntries,
|
||||
discovery_keys: tuple,
|
||||
@ -3043,7 +3107,7 @@ async def test_ignored_entry_update_discovery_keys(
|
||||
flow_result: data_entry_flow.FlowResultType,
|
||||
updated_discovery_keys: tuple,
|
||||
) -> None:
|
||||
"""Test that discovery keys of an ignored entry can be updated."""
|
||||
"""Test that discovery keys of an entry can be updated."""
|
||||
hass.config.components.add("comp")
|
||||
entry = MockConfigEntry(
|
||||
domain="comp",
|
||||
|
Loading…
x
Reference in New Issue
Block a user