Ensure entry_id is set on reauth/reconfigure flows (#129319)

* Ensure entry_id is set on reauth/reconfigure flows

* Improve

* Improve

* Use report helper

* Adjust deprecation date

* Update config_entries.py

* Improve message and adjust tests

* Apply suggestions from code review

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
epenet 2024-11-01 10:29:58 +01:00 committed by GitHub
parent 5430eca93e
commit b626c9b450
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 86 additions and 5 deletions

View File

@ -1260,13 +1260,24 @@ class ConfigEntriesFlowManager(
if not context or "source" not in context: if not context or "source" not in context:
raise KeyError("Context not set or doesn't have a source set") raise KeyError("Context not set or doesn't have a source set")
# reauth/reconfigure flows should be linked to a config entry
if (source := context["source"]) in {
SOURCE_REAUTH,
SOURCE_RECONFIGURE,
} and "entry_id" not in context:
# Deprecated in 2024.12, should fail in 2025.12
report(
f"initialises a {source} flow without a link to the config entry",
error_if_integration=False,
error_if_core=True,
)
flow_id = ulid_util.ulid_now() flow_id = ulid_util.ulid_now()
# Avoid starting a config flow on an integration that only supports # Avoid starting a config flow on an integration that only supports
# a single config entry, but which already has an entry # a single config entry, but which already has an entry
if ( if (
context.get("source") source not in {SOURCE_IGNORE, SOURCE_REAUTH, SOURCE_RECONFIGURE}
not in {SOURCE_IGNORE, SOURCE_REAUTH, SOURCE_RECONFIGURE}
and self.config_entries.async_has_entries(handler, include_ignore=False) and self.config_entries.async_has_entries(handler, include_ignore=False)
and await _support_single_config_entry_only(self.hass, handler) and await _support_single_config_entry_only(self.hass, handler)
): ):
@ -1280,7 +1291,7 @@ class ConfigEntriesFlowManager(
loop = self.hass.loop loop = self.hass.loop
if context["source"] == SOURCE_IMPORT: if source == SOURCE_IMPORT:
self._pending_import_flows[handler][flow_id] = loop.create_future() self._pending_import_flows[handler][flow_id] = loop.create_future()
cancel_init_future = loop.create_future() cancel_init_future = loop.create_future()

View File

@ -37,7 +37,7 @@ from homeassistant.exceptions import (
ConfigEntryNotReady, ConfigEntryNotReady,
HomeAssistantError, HomeAssistantError,
) )
from homeassistant.helpers import entity_registry as er, issue_registry as ir from homeassistant.helpers import entity_registry as er, frame, issue_registry as ir
from homeassistant.helpers.discovery_flow import DiscoveryKey from homeassistant.helpers.discovery_flow import DiscoveryKey
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.json import json_dumps from homeassistant.helpers.json import json_dumps
@ -4779,6 +4779,74 @@ async def test_reauth(
assert len(hass.config_entries.flow.async_progress()) == 1 assert len(hass.config_entries.flow.async_progress()) == 1
@pytest.mark.parametrize(
"source", [config_entries.SOURCE_REAUTH, config_entries.SOURCE_RECONFIGURE]
)
async def test_reauth_reconfigure_missing_entry(
hass: HomeAssistant,
manager: config_entries.ConfigEntries,
source: str,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test the async_reauth_helper."""
entry = MockConfigEntry(title="test_title", domain="test")
entry.add_to_hass(hass)
mock_setup_entry = AsyncMock(return_value=True)
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
mock_platform(hass, "test.config_flow", None)
await manager.async_setup(entry.entry_id)
await hass.async_block_till_done()
with pytest.raises(
RuntimeError,
match=f"Detected code that initialises a {source} flow without a link "
"to the config entry. Please report this issue.",
):
await manager.flow.async_init("test", context={"source": source})
await hass.async_block_till_done()
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 0
@pytest.mark.usefixtures("mock_integration_frame")
@pytest.mark.parametrize(
"source", [config_entries.SOURCE_REAUTH, config_entries.SOURCE_RECONFIGURE]
)
async def test_reauth_reconfigure_missing_entry_component(
hass: HomeAssistant,
manager: config_entries.ConfigEntries,
source: str,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test the async_reauth_helper."""
entry = MockConfigEntry(title="test_title", domain="test")
entry.add_to_hass(hass)
mock_setup_entry = AsyncMock(return_value=True)
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
mock_platform(hass, "test.config_flow", None)
await manager.async_setup(entry.entry_id)
await hass.async_block_till_done()
with patch.object(frame, "_REPORTED_INTEGRATIONS", set()):
await manager.flow.async_init("test", context={"source": source})
await hass.async_block_till_done()
# Flow still created, but deprecation logged
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1
assert flows[0]["context"]["source"] == source
assert (
f"Detected that integration 'hue' initialises a {source} flow"
" without a link to the config entry at homeassistant/components" in caplog.text
)
async def test_reconfigure( async def test_reconfigure(
hass: HomeAssistant, manager: config_entries.ConfigEntries hass: HomeAssistant, manager: config_entries.ConfigEntries
) -> None: ) -> None:
@ -5012,7 +5080,9 @@ async def test_initializing_flows_canceled_on_shutdown(
config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler} config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler}
): ):
task = asyncio.create_task( task = asyncio.create_task(
manager.flow.async_init("test", context={"source": "reauth"}) manager.flow.async_init(
"test", context={"source": "reauth", "entry_id": "abc"}
)
) )
await hass.async_block_till_done() await hass.async_block_till_done()
manager.flow.async_shutdown() manager.flow.async_shutdown()