2018-02-16 22:07:38 +00:00
|
|
|
"""Test the config manager."""
|
2024-03-08 15:36:11 +00:00
|
|
|
|
2022-05-31 03:24:34 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2018-02-16 22:07:38 +00:00
|
|
|
import asyncio
|
2024-07-01 09:51:51 +00:00
|
|
|
from collections.abc import Generator
|
2018-06-25 16:53:49 +00:00
|
|
|
from datetime import timedelta
|
2024-04-04 09:24:26 +00:00
|
|
|
from functools import cached_property
|
2021-04-10 05:41:29 +00:00
|
|
|
import logging
|
2023-02-21 08:27:13 +00:00
|
|
|
from typing import Any
|
2024-01-31 14:05:52 +00:00
|
|
|
from unittest.mock import ANY, AsyncMock, Mock, patch
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2024-02-24 07:46:00 +00:00
|
|
|
from freezegun.api import FrozenDateTimeFactory
|
2018-02-16 22:07:38 +00:00
|
|
|
import pytest
|
2023-12-11 15:48:12 +00:00
|
|
|
from syrupy.assertion import SnapshotAssertion
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2019-06-20 20:22:12 +00:00
|
|
|
from homeassistant import config_entries, data_entry_flow, loader
|
2022-05-31 03:24:34 +00:00
|
|
|
from homeassistant.components import dhcp
|
2021-12-03 13:05:56 +00:00
|
|
|
from homeassistant.components.hassio import HassioServiceInfo
|
2022-04-06 23:06:22 +00:00
|
|
|
from homeassistant.const import (
|
|
|
|
EVENT_COMPONENT_LOADED,
|
|
|
|
EVENT_HOMEASSISTANT_STARTED,
|
|
|
|
EVENT_HOMEASSISTANT_STOP,
|
|
|
|
)
|
2024-02-18 20:17:41 +00:00
|
|
|
from homeassistant.core import DOMAIN as HA_DOMAIN, CoreState, HomeAssistant, callback
|
2022-07-07 19:28:18 +00:00
|
|
|
from homeassistant.data_entry_flow import BaseServiceInfo, FlowResult, FlowResultType
|
2021-04-10 05:41:29 +00:00
|
|
|
from homeassistant.exceptions import (
|
|
|
|
ConfigEntryAuthFailed,
|
2022-11-25 10:33:03 +00:00
|
|
|
ConfigEntryError,
|
2021-04-10 05:41:29 +00:00
|
|
|
ConfigEntryNotReady,
|
|
|
|
HomeAssistantError,
|
|
|
|
)
|
2024-01-31 14:05:52 +00:00
|
|
|
from homeassistant.helpers import entity_registry as er, issue_registry as ir
|
2023-03-16 10:08:47 +00:00
|
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from homeassistant.helpers.typing import ConfigType
|
2021-04-10 05:41:29 +00:00
|
|
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
2022-11-17 20:52:57 +00:00
|
|
|
from homeassistant.setup import async_set_domains_to_be_loaded, async_setup_component
|
2024-06-13 01:06:11 +00:00
|
|
|
from homeassistant.util.async_ import create_eager_task
|
2023-02-27 03:01:02 +00:00
|
|
|
import homeassistant.util.dt as dt_util
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2023-01-27 11:51:58 +00:00
|
|
|
from .common import (
|
2019-07-31 19:25:30 +00:00
|
|
|
MockConfigEntry,
|
|
|
|
MockEntity,
|
2019-12-09 15:52:24 +00:00
|
|
|
MockModule,
|
|
|
|
MockPlatform,
|
2024-02-18 20:17:41 +00:00
|
|
|
async_capture_events,
|
2019-12-09 15:52:24 +00:00
|
|
|
async_fire_time_changed,
|
2024-06-11 13:04:00 +00:00
|
|
|
async_get_persistent_notifications,
|
2023-03-29 15:20:51 +00:00
|
|
|
mock_config_flow,
|
2019-12-09 15:52:24 +00:00
|
|
|
mock_integration,
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
@pytest.fixture(autouse=True)
|
2024-06-06 15:24:22 +00:00
|
|
|
def mock_handlers() -> Generator[None]:
|
2019-04-11 08:26:36 +00:00
|
|
|
"""Mock config flows."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
class MockFlowHandler(config_entries.ConfigFlow):
|
|
|
|
"""Define a mock flow handler."""
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2019-04-11 08:26:36 +00:00
|
|
|
VERSION = 1
|
|
|
|
|
2021-04-10 05:41:29 +00:00
|
|
|
async def async_step_reauth(self, data):
|
|
|
|
"""Mock Reauth."""
|
2024-01-31 14:05:52 +00:00
|
|
|
return await self.async_step_reauth_confirm()
|
|
|
|
|
|
|
|
async def async_step_reauth_confirm(self, user_input=None):
|
|
|
|
"""Test reauth confirm step."""
|
|
|
|
if user_input is None:
|
|
|
|
return self.async_show_form(step_id="reauth_confirm")
|
|
|
|
return self.async_abort(reason="test")
|
2021-04-10 05:41:29 +00:00
|
|
|
|
2024-03-01 11:29:35 +00:00
|
|
|
async def async_step_reconfigure(self, data):
|
|
|
|
"""Mock Reauth."""
|
|
|
|
return await self.async_step_reauth_confirm()
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch.dict(
|
|
|
|
config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler}
|
|
|
|
):
|
2019-04-11 08:26:36 +00:00
|
|
|
yield
|
2019-02-15 17:30:47 +00:00
|
|
|
|
|
|
|
|
2018-02-16 22:07:38 +00:00
|
|
|
@pytest.fixture
|
2023-06-16 02:15:07 +00:00
|
|
|
async def manager(hass: HomeAssistant) -> config_entries.ConfigEntries:
|
2018-02-16 22:07:38 +00:00
|
|
|
"""Fixture of a loaded config manager."""
|
|
|
|
manager = config_entries.ConfigEntries(hass, {})
|
2023-06-16 02:15:07 +00:00
|
|
|
await manager.async_initialize()
|
2018-02-16 22:07:38 +00:00
|
|
|
hass.config_entries = manager
|
|
|
|
return manager
|
|
|
|
|
|
|
|
|
2024-05-20 07:47:47 +00:00
|
|
|
async def test_setup_race_only_setup_once(hass: HomeAssistant) -> None:
|
|
|
|
"""Test ensure that config entries are only setup once."""
|
|
|
|
attempts = 0
|
|
|
|
slow_config_entry_setup_future = hass.loop.create_future()
|
|
|
|
fast_config_entry_setup_future = hass.loop.create_future()
|
|
|
|
slow_setup_future = hass.loop.create_future()
|
|
|
|
|
|
|
|
async def async_setup(hass, config):
|
|
|
|
"""Mock setup."""
|
|
|
|
await slow_setup_future
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def async_setup_entry(hass, entry):
|
|
|
|
"""Mock setup entry."""
|
|
|
|
slow = entry.data["slow"]
|
|
|
|
if slow:
|
|
|
|
await slow_config_entry_setup_future
|
|
|
|
return True
|
|
|
|
nonlocal attempts
|
|
|
|
attempts += 1
|
|
|
|
if attempts == 1:
|
|
|
|
raise ConfigEntryNotReady
|
|
|
|
await fast_config_entry_setup_future
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def async_unload_entry(hass, entry):
|
|
|
|
"""Mock unload entry."""
|
|
|
|
return True
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
|
|
|
|
entry = MockConfigEntry(domain="comp", data={"slow": False})
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
entry2 = MockConfigEntry(domain="comp", data={"slow": True})
|
|
|
|
entry2.add_to_hass(hass)
|
|
|
|
await entry2.setup_lock.acquire()
|
|
|
|
|
|
|
|
async def _async_reload_entry(entry: MockConfigEntry):
|
|
|
|
async with entry.setup_lock:
|
|
|
|
await entry.async_unload(hass)
|
|
|
|
await entry.async_setup(hass)
|
|
|
|
|
|
|
|
hass.async_create_task(_async_reload_entry(entry2))
|
|
|
|
|
|
|
|
setup_task = hass.async_create_task(async_setup_component(hass, "comp", {}))
|
|
|
|
entry2.setup_lock.release()
|
|
|
|
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
|
|
|
assert entry2.state is config_entries.ConfigEntryState.NOT_LOADED
|
|
|
|
|
|
|
|
assert "comp" not in hass.config.components
|
|
|
|
slow_setup_future.set_result(None)
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
assert "comp" in hass.config.components
|
|
|
|
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
|
|
|
|
assert entry2.state is config_entries.ConfigEntryState.SETUP_IN_PROGRESS
|
|
|
|
|
|
|
|
fast_config_entry_setup_future.set_result(None)
|
|
|
|
# Make sure setup retry is started
|
|
|
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=5))
|
|
|
|
slow_config_entry_setup_future.set_result(None)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert attempts == 2
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert setup_task.done()
|
|
|
|
assert entry2.state is config_entries.ConfigEntryState.LOADED
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_call_setup_entry(hass: HomeAssistant) -> None:
|
2018-02-16 22:07:38 +00:00
|
|
|
"""Test we call <component>.setup_entry."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entry = MockConfigEntry(domain="comp")
|
2019-02-15 17:30:47 +00:00
|
|
|
entry.add_to_hass(hass)
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
mock_migrate_entry = AsyncMock(return_value=True)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2019-04-15 02:07:05 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
2019-07-31 19:25:30 +00:00
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_migrate_entry=mock_migrate_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2020-08-25 22:59:22 +00:00
|
|
|
with patch("homeassistant.config_entries.support_entry_unload", return_value=True):
|
|
|
|
result = await async_setup_component(hass, "comp", {})
|
|
|
|
await hass.async_block_till_done()
|
2019-02-15 17:30:47 +00:00
|
|
|
assert result
|
|
|
|
assert len(mock_migrate_entry.mock_calls) == 0
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2020-08-25 22:59:22 +00:00
|
|
|
assert entry.supports_unload
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_call_setup_entry_without_reload_support(hass: HomeAssistant) -> None:
|
2020-08-25 22:59:22 +00:00
|
|
|
"""Test we call <component>.setup_entry and the <component> does not support unloading."""
|
|
|
|
entry = MockConfigEntry(domain="comp")
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
assert not entry.supports_unload
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
mock_migrate_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_migrate_entry=mock_migrate_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-08-25 22:59:22 +00:00
|
|
|
|
|
|
|
with patch("homeassistant.config_entries.support_entry_unload", return_value=False):
|
|
|
|
result = await async_setup_component(hass, "comp", {})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert result
|
|
|
|
assert len(mock_migrate_entry.mock_calls) == 0
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2019-02-15 17:30:47 +00:00
|
|
|
|
|
|
|
|
2023-12-12 07:44:35 +00:00
|
|
|
@pytest.mark.parametrize(("major_version", "minor_version"), [(2, 1), (1, 2), (2, 2)])
|
|
|
|
async def test_call_async_migrate_entry(
|
|
|
|
hass: HomeAssistant, major_version: int, minor_version: int
|
|
|
|
) -> None:
|
2019-02-15 17:30:47 +00:00
|
|
|
"""Test we call <component>.async_migrate_entry when version mismatch."""
|
2024-02-16 16:15:05 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", version=major_version, minor_version=minor_version
|
|
|
|
)
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2024-02-16 16:15:05 +00:00
|
|
|
|
2019-02-15 17:30:47 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_migrate_entry = AsyncMock(return_value=True)
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2019-04-15 02:07:05 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
2019-07-31 19:25:30 +00:00
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_migrate_entry=mock_migrate_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2020-08-25 22:59:22 +00:00
|
|
|
with patch("homeassistant.config_entries.support_entry_unload", return_value=True):
|
|
|
|
result = await async_setup_component(hass, "comp", {})
|
|
|
|
await hass.async_block_till_done()
|
2018-02-16 22:07:38 +00:00
|
|
|
assert result
|
2019-02-15 17:30:47 +00:00
|
|
|
assert len(mock_migrate_entry.mock_calls) == 1
|
2018-02-16 22:07:38 +00:00
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2020-08-25 22:59:22 +00:00
|
|
|
assert entry.supports_unload
|
2019-02-15 17:30:47 +00:00
|
|
|
|
|
|
|
|
2023-12-12 07:44:35 +00:00
|
|
|
@pytest.mark.parametrize(("major_version", "minor_version"), [(2, 1), (1, 2), (2, 2)])
|
|
|
|
async def test_call_async_migrate_entry_failure_false(
|
|
|
|
hass: HomeAssistant, major_version: int, minor_version: int
|
|
|
|
) -> None:
|
2019-02-15 17:30:47 +00:00
|
|
|
"""Test migration fails if returns false."""
|
2024-02-16 16:15:05 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", version=major_version, minor_version=minor_version
|
|
|
|
)
|
2019-02-15 17:30:47 +00:00
|
|
|
entry.add_to_hass(hass)
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_migrate_entry = AsyncMock(return_value=False)
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2019-04-15 02:07:05 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
2019-07-31 19:25:30 +00:00
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_migrate_entry=mock_migrate_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
result = await async_setup_component(hass, "comp", {})
|
2019-02-15 17:30:47 +00:00
|
|
|
assert result
|
|
|
|
assert len(mock_migrate_entry.mock_calls) == 1
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 0
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.MIGRATION_ERROR
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2019-02-15 17:30:47 +00:00
|
|
|
|
|
|
|
|
2023-12-12 07:44:35 +00:00
|
|
|
@pytest.mark.parametrize(("major_version", "minor_version"), [(2, 1), (1, 2), (2, 2)])
|
|
|
|
async def test_call_async_migrate_entry_failure_exception(
|
|
|
|
hass: HomeAssistant, major_version: int, minor_version: int
|
|
|
|
) -> None:
|
2019-02-15 17:30:47 +00:00
|
|
|
"""Test migration fails if exception raised."""
|
2024-02-16 16:15:05 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", version=major_version, minor_version=minor_version
|
|
|
|
)
|
2019-02-15 17:30:47 +00:00
|
|
|
entry.add_to_hass(hass)
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_migrate_entry = AsyncMock(side_effect=Exception)
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2019-04-15 02:07:05 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
2019-07-31 19:25:30 +00:00
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_migrate_entry=mock_migrate_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
result = await async_setup_component(hass, "comp", {})
|
2019-02-15 17:30:47 +00:00
|
|
|
assert result
|
|
|
|
assert len(mock_migrate_entry.mock_calls) == 1
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 0
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.MIGRATION_ERROR
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2019-02-15 17:30:47 +00:00
|
|
|
|
|
|
|
|
2023-12-12 07:44:35 +00:00
|
|
|
@pytest.mark.parametrize(("major_version", "minor_version"), [(2, 1), (1, 2), (2, 2)])
|
|
|
|
async def test_call_async_migrate_entry_failure_not_bool(
|
|
|
|
hass: HomeAssistant, major_version: int, minor_version: int
|
|
|
|
) -> None:
|
2019-02-15 17:30:47 +00:00
|
|
|
"""Test migration fails if boolean not returned."""
|
2024-02-16 16:15:05 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", version=major_version, minor_version=minor_version
|
|
|
|
)
|
2019-02-15 17:30:47 +00:00
|
|
|
entry.add_to_hass(hass)
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_migrate_entry = AsyncMock(return_value=None)
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2019-04-15 02:07:05 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
2019-07-31 19:25:30 +00:00
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_migrate_entry=mock_migrate_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
result = await async_setup_component(hass, "comp", {})
|
2019-02-15 17:30:47 +00:00
|
|
|
assert result
|
|
|
|
assert len(mock_migrate_entry.mock_calls) == 1
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 0
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.MIGRATION_ERROR
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2019-02-15 17:30:47 +00:00
|
|
|
|
|
|
|
|
2023-12-12 07:44:35 +00:00
|
|
|
@pytest.mark.parametrize(("major_version", "minor_version"), [(2, 1), (2, 2)])
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_call_async_migrate_entry_failure_not_supported(
|
2023-12-12 07:44:35 +00:00
|
|
|
hass: HomeAssistant, major_version: int, minor_version: int
|
2023-02-08 07:51:43 +00:00
|
|
|
) -> None:
|
2019-02-15 17:30:47 +00:00
|
|
|
"""Test migration fails if async_migrate_entry not implemented."""
|
2024-02-16 16:15:05 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", version=major_version, minor_version=minor_version
|
|
|
|
)
|
2019-02-15 17:30:47 +00:00
|
|
|
entry.add_to_hass(hass)
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-02-15 17:30:47 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
result = await async_setup_component(hass, "comp", {})
|
2019-02-15 17:30:47 +00:00
|
|
|
assert result
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 0
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.MIGRATION_ERROR
|
2020-08-25 22:59:22 +00:00
|
|
|
assert not entry.supports_unload
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
|
2023-12-12 07:44:35 +00:00
|
|
|
@pytest.mark.parametrize(("major_version", "minor_version"), [(1, 2)])
|
|
|
|
async def test_call_async_migrate_entry_not_supported_minor_version(
|
|
|
|
hass: HomeAssistant, major_version: int, minor_version: int
|
|
|
|
) -> None:
|
|
|
|
"""Test migration without async_migrate_entry and minor version changed."""
|
2024-02-16 16:15:05 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", version=major_version, minor_version=minor_version
|
|
|
|
)
|
2023-12-12 07:44:35 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
assert not entry.supports_unload
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry))
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
|
|
|
|
result = await async_setup_component(hass, "comp", {})
|
|
|
|
assert result
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
|
|
|
assert not entry.supports_unload
|
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_remove_entry(
|
2023-11-10 08:32:19 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
entity_registry: er.EntityRegistry,
|
2023-03-16 10:08:47 +00:00
|
|
|
) -> None:
|
2018-02-16 22:07:38 +00:00
|
|
|
"""Test that we can remove an entry."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def mock_setup_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
2018-10-25 17:57:36 +00:00
|
|
|
"""Mock setting up entry."""
|
2023-04-24 03:38:35 +00:00
|
|
|
await hass.config_entries.async_forward_entry_setups(entry, ["light"])
|
2018-10-25 17:57:36 +00:00
|
|
|
return True
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def mock_unload_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
2018-10-25 17:57:36 +00:00
|
|
|
"""Mock unloading an entry."""
|
2021-04-26 17:46:55 +00:00
|
|
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
2018-10-25 17:57:36 +00:00
|
|
|
assert result
|
|
|
|
return result
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_remove_entry = AsyncMock(return_value=None)
|
2019-03-02 05:13:55 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
entity = MockEntity(unique_id="1234", name="Test Entity")
|
2018-10-25 17:57:36 +00:00
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def mock_setup_entry_platform(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entry: config_entries.ConfigEntry,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
) -> None:
|
2018-10-25 17:57:36 +00:00
|
|
|
"""Mock setting up platform."""
|
|
|
|
async_add_entities([entity])
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"test",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=mock_unload_entry,
|
|
|
|
async_remove_entry=mock_remove_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(
|
|
|
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
2018-10-25 17:57:36 +00:00
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2019-07-31 19:25:30 +00:00
|
|
|
|
|
|
|
MockConfigEntry(domain="test_other", entry_id="test1").add_to_manager(manager)
|
|
|
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
2018-10-25 17:57:36 +00:00
|
|
|
entry.add_to_manager(manager)
|
2019-07-31 19:25:30 +00:00
|
|
|
MockConfigEntry(domain="test_other", entry_id="test3").add_to_manager(manager)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2018-10-25 17:57:36 +00:00
|
|
|
# Check all config entries exist
|
2024-03-05 01:59:12 +00:00
|
|
|
assert manager.async_entry_ids() == [
|
2019-07-31 19:25:30 +00:00
|
|
|
"test1",
|
|
|
|
"test2",
|
|
|
|
"test3",
|
|
|
|
]
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2018-10-25 17:57:36 +00:00
|
|
|
# Setup entry
|
2024-05-10 22:09:28 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2018-10-25 17:57:36 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check entity state got added
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.states.get("light.test_entity") is not None
|
2020-01-07 16:30:53 +00:00
|
|
|
assert len(hass.states.async_all()) == 1
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2018-10-25 17:57:36 +00:00
|
|
|
# Check entity got added to entity registry
|
2023-11-10 08:32:19 +00:00
|
|
|
assert len(entity_registry.entities) == 1
|
|
|
|
entity_entry = list(entity_registry.entities.values())[0]
|
2018-10-25 17:57:36 +00:00
|
|
|
assert entity_entry.config_entry_id == entry.entry_id
|
|
|
|
|
|
|
|
# Remove entry
|
2019-07-31 19:25:30 +00:00
|
|
|
result = await manager.async_remove("test2")
|
2018-10-25 17:57:36 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check that unload went well and so no need to restart
|
2019-07-31 19:25:30 +00:00
|
|
|
assert result == {"require_restart": False}
|
2018-10-25 17:57:36 +00:00
|
|
|
|
2019-03-02 05:13:55 +00:00
|
|
|
# Check the remove callback was invoked.
|
|
|
|
assert mock_remove_entry.call_count == 1
|
|
|
|
|
2018-10-25 17:57:36 +00:00
|
|
|
# Check that config entry was removed.
|
2024-03-05 01:59:12 +00:00
|
|
|
assert manager.async_entry_ids() == ["test1", "test3"]
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2018-10-25 17:57:36 +00:00
|
|
|
# Check that entity state has been removed
|
2019-07-31 19:25:30 +00:00
|
|
|
assert hass.states.get("light.test_entity") is None
|
2020-01-07 16:30:53 +00:00
|
|
|
assert len(hass.states.async_all()) == 0
|
2018-10-25 17:57:36 +00:00
|
|
|
|
2019-05-19 09:41:39 +00:00
|
|
|
# Check that entity registry entry has been removed
|
2023-11-10 08:32:19 +00:00
|
|
|
entity_entry_list = list(entity_registry.entities.values())
|
2019-05-19 09:41:39 +00:00
|
|
|
assert not entity_entry_list
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_remove_entry_cancels_reauth(
|
2024-05-24 13:26:32 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
issue_registry: ir.IssueRegistry,
|
2023-03-16 10:08:47 +00:00
|
|
|
) -> None:
|
2021-07-02 18:56:51 +00:00
|
|
|
"""Tests that removing a config entry, also aborts existing reauth flows."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(side_effect=ConfigEntryAuthFailed())
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2021-07-02 18:56:51 +00:00
|
|
|
|
|
|
|
entry.add_to_hass(hass)
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2021-07-02 18:56:51 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2021-10-22 17:19:49 +00:00
|
|
|
flows = hass.config_entries.flow.async_progress_by_handler("test")
|
2021-07-02 18:56:51 +00:00
|
|
|
assert len(flows) == 1
|
|
|
|
assert flows[0]["context"]["entry_id"] == entry.entry_id
|
|
|
|
assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR
|
|
|
|
|
2024-01-31 14:05:52 +00:00
|
|
|
issue_id = f"config_entry_reauth_test_{entry.entry_id}"
|
|
|
|
assert issue_registry.async_get_issue(HA_DOMAIN, issue_id)
|
|
|
|
|
2021-07-02 18:56:51 +00:00
|
|
|
await manager.async_remove(entry.entry_id)
|
|
|
|
|
2021-10-22 17:19:49 +00:00
|
|
|
flows = hass.config_entries.flow.async_progress_by_handler("test")
|
2021-07-02 18:56:51 +00:00
|
|
|
assert len(flows) == 0
|
2024-01-31 14:05:52 +00:00
|
|
|
assert not issue_registry.async_get_issue(HA_DOMAIN, issue_id)
|
2021-07-02 18:56:51 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_remove_entry_handles_callback_error(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2019-03-02 05:13:55 +00:00
|
|
|
"""Test that exceptions in the remove callback are handled."""
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
mock_unload_entry = AsyncMock(return_value=True)
|
|
|
|
mock_remove_entry = AsyncMock(return_value=None)
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"test",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=mock_unload_entry,
|
|
|
|
async_remove_entry=mock_remove_entry,
|
|
|
|
),
|
2019-03-02 05:13:55 +00:00
|
|
|
)
|
2019-07-31 19:25:30 +00:00
|
|
|
entry = MockConfigEntry(domain="test", entry_id="test1")
|
2019-03-02 05:13:55 +00:00
|
|
|
entry.add_to_manager(manager)
|
|
|
|
# Check all config entries exist
|
2024-03-05 01:59:12 +00:00
|
|
|
assert manager.async_entry_ids() == ["test1"]
|
2019-03-02 05:13:55 +00:00
|
|
|
# Setup entry
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2019-03-02 05:13:55 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Remove entry
|
2019-07-31 19:25:30 +00:00
|
|
|
result = await manager.async_remove("test1")
|
2019-03-02 05:13:55 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
# Check that unload went well and so no need to restart
|
2019-07-31 19:25:30 +00:00
|
|
|
assert result == {"require_restart": False}
|
2019-03-02 05:13:55 +00:00
|
|
|
# Check the remove callback was invoked.
|
|
|
|
assert mock_remove_entry.call_count == 1
|
|
|
|
# Check that config entry was removed.
|
2024-03-05 01:59:12 +00:00
|
|
|
assert manager.async_entry_ids() == []
|
2019-03-02 05:13:55 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_remove_entry_raises(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2018-02-16 22:07:38 +00:00
|
|
|
"""Test if a component raises while removing entry."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2020-04-07 16:33:23 +00:00
|
|
|
async def mock_unload_entry(hass, entry):
|
2018-02-16 22:07:38 +00:00
|
|
|
"""Mock unload entry function."""
|
2024-06-12 10:21:41 +00:00
|
|
|
raise Exception("BROKEN") # pylint: disable=broad-exception-raised
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_unload_entry=mock_unload_entry))
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
MockConfigEntry(domain="test", entry_id="test1").add_to_manager(manager)
|
2018-09-25 12:29:13 +00:00
|
|
|
MockConfigEntry(
|
2021-05-20 17:19:20 +00:00
|
|
|
domain="comp", entry_id="test2", state=config_entries.ConfigEntryState.LOADED
|
2018-09-25 12:29:13 +00:00
|
|
|
).add_to_manager(manager)
|
2019-07-31 19:25:30 +00:00
|
|
|
MockConfigEntry(domain="test", entry_id="test3").add_to_manager(manager)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2024-03-05 01:59:12 +00:00
|
|
|
assert manager.async_entry_ids() == [
|
2019-07-31 19:25:30 +00:00
|
|
|
"test1",
|
|
|
|
"test2",
|
|
|
|
"test3",
|
|
|
|
]
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2020-04-07 16:33:23 +00:00
|
|
|
result = await manager.async_remove("test2")
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert result == {"require_restart": True}
|
2024-03-05 01:59:12 +00:00
|
|
|
assert manager.async_entry_ids() == ["test1", "test3"]
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_remove_entry_if_not_loaded(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-04-15 02:07:05 +00:00
|
|
|
"""Test that we can remove an entry that is not loaded."""
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_unload_entry = AsyncMock(return_value=True)
|
2018-09-25 12:29:13 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_unload_entry=mock_unload_entry))
|
2018-09-25 12:29:13 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
MockConfigEntry(domain="test", entry_id="test1").add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="comp", entry_id="test2").add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="test", entry_id="test3").add_to_manager(manager)
|
2018-09-25 12:29:13 +00:00
|
|
|
|
2024-03-05 01:59:12 +00:00
|
|
|
assert manager.async_entry_ids() == [
|
2019-07-31 19:25:30 +00:00
|
|
|
"test1",
|
|
|
|
"test2",
|
|
|
|
"test3",
|
|
|
|
]
|
2018-09-25 12:29:13 +00:00
|
|
|
|
2020-04-07 16:33:23 +00:00
|
|
|
result = await manager.async_remove("test2")
|
2018-09-25 12:29:13 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert result == {"require_restart": False}
|
2024-03-05 01:59:12 +00:00
|
|
|
assert manager.async_entry_ids() == ["test1", "test3"]
|
2020-05-25 19:40:06 +00:00
|
|
|
|
|
|
|
assert len(mock_unload_entry.mock_calls) == 0
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_remove_entry_if_integration_deleted(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2020-05-25 19:40:06 +00:00
|
|
|
"""Test that we can remove an entry when the integration is deleted."""
|
|
|
|
mock_unload_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
MockConfigEntry(domain="test", entry_id="test1").add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="comp", entry_id="test2").add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="test", entry_id="test3").add_to_manager(manager)
|
|
|
|
|
2024-03-05 01:59:12 +00:00
|
|
|
assert manager.async_entry_ids() == [
|
2020-05-25 19:40:06 +00:00
|
|
|
"test1",
|
|
|
|
"test2",
|
|
|
|
"test3",
|
|
|
|
]
|
|
|
|
|
|
|
|
result = await manager.async_remove("test2")
|
|
|
|
|
|
|
|
assert result == {"require_restart": False}
|
2024-03-05 01:59:12 +00:00
|
|
|
assert manager.async_entry_ids() == ["test1", "test3"]
|
2018-09-25 12:29:13 +00:00
|
|
|
|
2019-04-15 02:07:05 +00:00
|
|
|
assert len(mock_unload_entry.mock_calls) == 0
|
2018-09-25 12:29:13 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_add_entry_calls_setup_entry(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2018-02-16 22:07:38 +00:00
|
|
|
"""Test we call setup_config_entry."""
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2018-09-17 08:12:46 +00:00
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
2020-04-07 16:33:23 +00:00
|
|
|
async def async_step_user(self, user_input=None):
|
|
|
|
"""Test user step."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return self.async_create_entry(title="title", data={"token": "supersecret"})
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}):
|
2020-04-07 16:33:23 +00:00
|
|
|
await manager.flow.async_init(
|
2019-07-31 19:25:30 +00:00
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
2020-04-07 16:33:23 +00:00
|
|
|
await hass.async_block_till_done()
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
p_hass, p_entry = mock_setup_entry.mock_calls[0][1]
|
|
|
|
|
|
|
|
assert p_hass is hass
|
2019-07-31 19:25:30 +00:00
|
|
|
assert p_entry.data == {"token": "supersecret"}
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entries_gets_entries(manager: config_entries.ConfigEntries) -> None:
|
2018-02-16 22:07:38 +00:00
|
|
|
"""Test entries are filtered by domain."""
|
2019-07-31 19:25:30 +00:00
|
|
|
MockConfigEntry(domain="test").add_to_manager(manager)
|
|
|
|
entry1 = MockConfigEntry(domain="test2")
|
2018-02-16 22:07:38 +00:00
|
|
|
entry1.add_to_manager(manager)
|
2019-07-31 19:25:30 +00:00
|
|
|
entry2 = MockConfigEntry(domain="test2")
|
2018-02-16 22:07:38 +00:00
|
|
|
entry2.add_to_manager(manager)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert manager.async_entries("test2") == [entry1, entry2]
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_domains_gets_domains_uniques(
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
) -> None:
|
2018-02-16 22:07:38 +00:00
|
|
|
"""Test we only return each domain once."""
|
2019-07-31 19:25:30 +00:00
|
|
|
MockConfigEntry(domain="test").add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="test2").add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="test2").add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="test").add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="test3").add_to_manager(manager)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
assert manager.async_domains() == ["test", "test2", "test3"]
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_domains_gets_domains_excludes_ignore_and_disabled(
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
) -> None:
|
2021-03-29 11:06:44 +00:00
|
|
|
"""Test we only return each domain once."""
|
|
|
|
MockConfigEntry(domain="test").add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="test2").add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="test2").add_to_manager(manager)
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="ignored", source=config_entries.SOURCE_IGNORE
|
|
|
|
).add_to_manager(manager)
|
|
|
|
MockConfigEntry(domain="test3").add_to_manager(manager)
|
2021-04-23 07:56:42 +00:00
|
|
|
MockConfigEntry(
|
2021-12-15 19:53:21 +00:00
|
|
|
domain="disabled", disabled_by=config_entries.ConfigEntryDisabler.USER
|
2021-04-23 07:56:42 +00:00
|
|
|
).add_to_manager(manager)
|
2021-03-29 11:06:44 +00:00
|
|
|
assert manager.async_domains() == ["test", "test2", "test3"]
|
|
|
|
assert manager.async_domains(include_ignore=False) == ["test", "test2", "test3"]
|
|
|
|
assert manager.async_domains(include_disabled=False) == ["test", "test2", "test3"]
|
|
|
|
assert manager.async_domains(include_ignore=False, include_disabled=False) == [
|
|
|
|
"test",
|
|
|
|
"test2",
|
|
|
|
"test3",
|
|
|
|
]
|
|
|
|
|
|
|
|
assert manager.async_domains(include_ignore=True) == [
|
|
|
|
"test",
|
|
|
|
"test2",
|
|
|
|
"ignored",
|
|
|
|
"test3",
|
|
|
|
]
|
|
|
|
assert manager.async_domains(include_disabled=True) == [
|
|
|
|
"test",
|
|
|
|
"test2",
|
|
|
|
"test3",
|
|
|
|
"disabled",
|
|
|
|
]
|
|
|
|
assert manager.async_domains(include_ignore=True, include_disabled=True) == [
|
|
|
|
"test",
|
|
|
|
"test2",
|
|
|
|
"ignored",
|
|
|
|
"test3",
|
|
|
|
"disabled",
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2024-02-17 09:34:03 +00:00
|
|
|
async def test_entries_excludes_ignore_and_disabled(
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
) -> None:
|
|
|
|
"""Test ignored and disabled entries are returned by default."""
|
|
|
|
entry = MockConfigEntry(domain="test")
|
|
|
|
entry.add_to_manager(manager)
|
|
|
|
entry2a = MockConfigEntry(domain="test2")
|
|
|
|
entry2a.add_to_manager(manager)
|
|
|
|
entry2b = MockConfigEntry(domain="test2")
|
|
|
|
entry2b.add_to_manager(manager)
|
|
|
|
entry_ignored = MockConfigEntry(
|
|
|
|
domain="ignored", source=config_entries.SOURCE_IGNORE
|
|
|
|
)
|
|
|
|
entry_ignored.add_to_manager(manager)
|
|
|
|
entry3 = MockConfigEntry(domain="test3")
|
|
|
|
entry3.add_to_manager(manager)
|
|
|
|
disabled_entry = MockConfigEntry(
|
|
|
|
domain="disabled", disabled_by=config_entries.ConfigEntryDisabler.USER
|
|
|
|
)
|
|
|
|
disabled_entry.add_to_manager(manager)
|
|
|
|
assert manager.async_entries() == [
|
|
|
|
entry,
|
|
|
|
entry2a,
|
|
|
|
entry2b,
|
|
|
|
entry_ignored,
|
|
|
|
entry3,
|
|
|
|
disabled_entry,
|
|
|
|
]
|
2024-05-11 07:47:17 +00:00
|
|
|
assert manager.async_has_entries("test") is True
|
|
|
|
assert manager.async_has_entries("test2") is True
|
|
|
|
assert manager.async_has_entries("test3") is True
|
|
|
|
assert manager.async_has_entries("ignored") is True
|
|
|
|
assert manager.async_has_entries("disabled") is True
|
|
|
|
|
|
|
|
assert manager.async_has_entries("not") is False
|
2024-02-17 09:34:03 +00:00
|
|
|
assert manager.async_entries(include_ignore=False) == [
|
|
|
|
entry,
|
|
|
|
entry2a,
|
|
|
|
entry2b,
|
|
|
|
entry3,
|
|
|
|
disabled_entry,
|
|
|
|
]
|
|
|
|
assert manager.async_entries(include_disabled=False) == [
|
|
|
|
entry,
|
|
|
|
entry2a,
|
|
|
|
entry2b,
|
|
|
|
entry_ignored,
|
|
|
|
entry3,
|
|
|
|
]
|
|
|
|
assert manager.async_entries(include_ignore=False, include_disabled=False) == [
|
|
|
|
entry,
|
|
|
|
entry2a,
|
|
|
|
entry2b,
|
|
|
|
entry3,
|
|
|
|
]
|
2024-05-11 07:47:17 +00:00
|
|
|
assert manager.async_has_entries("test", include_ignore=False) is True
|
|
|
|
assert manager.async_has_entries("test2", include_ignore=False) is True
|
|
|
|
assert manager.async_has_entries("test3", include_ignore=False) is True
|
|
|
|
assert manager.async_has_entries("ignored", include_ignore=False) is False
|
2024-02-17 09:34:03 +00:00
|
|
|
|
|
|
|
assert manager.async_entries(include_ignore=True) == [
|
|
|
|
entry,
|
|
|
|
entry2a,
|
|
|
|
entry2b,
|
|
|
|
entry_ignored,
|
|
|
|
entry3,
|
|
|
|
disabled_entry,
|
|
|
|
]
|
|
|
|
assert manager.async_entries(include_disabled=True) == [
|
|
|
|
entry,
|
|
|
|
entry2a,
|
|
|
|
entry2b,
|
|
|
|
entry_ignored,
|
|
|
|
entry3,
|
|
|
|
disabled_entry,
|
|
|
|
]
|
|
|
|
assert manager.async_entries(include_ignore=True, include_disabled=True) == [
|
|
|
|
entry,
|
|
|
|
entry2a,
|
|
|
|
entry2b,
|
|
|
|
entry_ignored,
|
|
|
|
entry3,
|
|
|
|
disabled_entry,
|
|
|
|
]
|
2024-05-11 07:47:17 +00:00
|
|
|
assert manager.async_has_entries("test", include_disabled=False) is True
|
|
|
|
assert manager.async_has_entries("test2", include_disabled=False) is True
|
|
|
|
assert manager.async_has_entries("test3", include_disabled=False) is True
|
|
|
|
assert manager.async_has_entries("disabled", include_disabled=False) is False
|
2024-02-17 09:34:03 +00:00
|
|
|
|
|
|
|
|
2024-02-24 07:46:00 +00:00
|
|
|
async def test_saving_and_loading(
|
|
|
|
hass: HomeAssistant, freezer: FrozenDateTimeFactory
|
|
|
|
) -> None:
|
2018-02-16 22:07:38 +00:00
|
|
|
"""Test that we're saving and loading correctly."""
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(
|
2023-08-25 20:54:55 +00:00
|
|
|
hass,
|
2024-02-24 07:46:00 +00:00
|
|
|
MockModule("test", async_setup_entry=AsyncMock(return_value=True)),
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2018-05-01 18:57:30 +00:00
|
|
|
|
2018-09-17 08:12:46 +00:00
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
|
|
|
|
2018-02-16 22:07:38 +00:00
|
|
|
VERSION = 5
|
|
|
|
|
2019-12-16 18:45:09 +00:00
|
|
|
async def async_step_user(self, user_input=None):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test user step."""
|
2019-12-16 18:45:09 +00:00
|
|
|
await self.async_set_unique_id("unique")
|
2019-07-31 19:25:30 +00:00
|
|
|
return self.async_create_entry(title="Test Title", data={"token": "abcd"})
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch.dict(config_entries.HANDLERS, {"test": TestFlow}):
|
2018-08-13 09:27:18 +00:00
|
|
|
await hass.config_entries.flow.async_init(
|
2019-07-31 19:25:30 +00:00
|
|
|
"test", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2018-09-17 08:12:46 +00:00
|
|
|
class Test2Flow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
|
|
|
|
2018-02-16 22:07:38 +00:00
|
|
|
VERSION = 3
|
|
|
|
|
2020-04-07 16:33:23 +00:00
|
|
|
async def async_step_user(self, user_input=None):
|
|
|
|
"""Test user step."""
|
2018-02-16 22:07:38 +00:00
|
|
|
return self.async_create_entry(
|
2019-07-31 19:25:30 +00:00
|
|
|
title="Test 2 Title", data={"username": "bla"}
|
2018-02-16 22:07:38 +00:00
|
|
|
)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch("homeassistant.config_entries.HANDLERS.get", return_value=Test2Flow):
|
2018-08-13 09:27:18 +00:00
|
|
|
await hass.config_entries.flow.async_init(
|
2019-07-31 19:25:30 +00:00
|
|
|
"test", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2020-03-09 21:07:50 +00:00
|
|
|
assert len(hass.config_entries.async_entries()) == 2
|
2021-06-01 20:34:31 +00:00
|
|
|
entry_1 = hass.config_entries.async_entries()[0]
|
|
|
|
|
|
|
|
hass.config_entries.async_update_entry(
|
|
|
|
entry_1,
|
|
|
|
pref_disable_polling=True,
|
|
|
|
pref_disable_new_entities=True,
|
|
|
|
)
|
2020-03-09 21:07:50 +00:00
|
|
|
|
2018-06-29 02:14:26 +00:00
|
|
|
# To trigger the call_later
|
2024-02-24 07:46:00 +00:00
|
|
|
freezer.tick(1.0)
|
|
|
|
async_fire_time_changed(hass)
|
2018-06-29 02:14:26 +00:00
|
|
|
# To execute the save
|
|
|
|
await hass.async_block_till_done()
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
# Now load written data in new config manager
|
|
|
|
manager = config_entries.ConfigEntries(hass, {})
|
2019-03-01 04:27:20 +00:00
|
|
|
await manager.async_initialize()
|
2018-02-16 22:07:38 +00:00
|
|
|
|
2020-03-09 21:07:50 +00:00
|
|
|
assert len(manager.async_entries()) == 2
|
|
|
|
|
2018-02-16 22:07:38 +00:00
|
|
|
# Ensure same order
|
2019-07-31 19:25:30 +00:00
|
|
|
for orig, loaded in zip(
|
2024-04-14 05:14:26 +00:00
|
|
|
hass.config_entries.async_entries(), manager.async_entries(), strict=False
|
2019-07-31 19:25:30 +00:00
|
|
|
):
|
2023-12-11 15:48:12 +00:00
|
|
|
assert orig.as_dict() == loaded.as_dict()
|
|
|
|
|
|
|
|
|
|
|
|
async def test_as_dict(snapshot: SnapshotAssertion) -> None:
|
|
|
|
"""Test ConfigEntry.as_dict."""
|
|
|
|
|
|
|
|
# Ensure as_dict is not overridden
|
|
|
|
assert MockConfigEntry.as_dict is config_entries.ConfigEntry.as_dict
|
|
|
|
|
|
|
|
excluded_from_dict = {
|
|
|
|
"supports_unload",
|
|
|
|
"supports_remove_device",
|
|
|
|
"state",
|
|
|
|
"_setup_lock",
|
|
|
|
"update_listeners",
|
|
|
|
"reason",
|
2024-03-28 09:52:21 +00:00
|
|
|
"error_reason_translation_key",
|
|
|
|
"error_reason_translation_placeholders",
|
2023-12-11 15:48:12 +00:00
|
|
|
"_async_cancel_retry_setup",
|
|
|
|
"_on_unload",
|
2024-04-30 23:47:12 +00:00
|
|
|
"setup_lock",
|
2023-12-11 15:48:12 +00:00
|
|
|
"_reauth_lock",
|
|
|
|
"_tasks",
|
|
|
|
"_background_tasks",
|
|
|
|
"_integration_for_domain",
|
|
|
|
"_tries",
|
|
|
|
"_setup_again_job",
|
2024-01-26 06:20:19 +00:00
|
|
|
"_supports_options",
|
2024-03-01 11:29:35 +00:00
|
|
|
"_reconfigure_lock",
|
|
|
|
"supports_reconfigure",
|
2023-12-11 15:48:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
entry = MockConfigEntry(entry_id="mock-entry")
|
|
|
|
|
|
|
|
# Make sure the expected keys are present
|
|
|
|
dict_repr = entry.as_dict()
|
2024-02-18 01:52:39 +00:00
|
|
|
for key in config_entries.ConfigEntry.__dict__:
|
|
|
|
func = getattr(config_entries.ConfigEntry, key)
|
|
|
|
if (
|
|
|
|
key.startswith("__")
|
|
|
|
or callable(func)
|
|
|
|
or type(func) in (cached_property, property)
|
|
|
|
):
|
|
|
|
continue
|
2023-12-11 15:48:12 +00:00
|
|
|
assert key in dict_repr or key in excluded_from_dict
|
|
|
|
assert not (key in dict_repr and key in excluded_from_dict)
|
|
|
|
|
|
|
|
# Make sure the dict representation is as expected
|
|
|
|
assert dict_repr == snapshot
|
2018-02-16 22:07:38 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_forward_entry_sets_up_component(hass: HomeAssistant) -> None:
|
2018-04-09 14:09:08 +00:00
|
|
|
"""Test we setup the component entry is forwarded to."""
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="original", state=config_entries.ConfigEntryState.LOADED
|
|
|
|
)
|
2018-04-09 14:09:08 +00:00
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_original_setup_entry = AsyncMock(return_value=True)
|
2024-03-23 19:26:38 +00:00
|
|
|
integration = mock_integration(
|
2019-07-31 19:25:30 +00:00
|
|
|
hass, MockModule("original", async_setup_entry=mock_original_setup_entry)
|
|
|
|
)
|
2018-04-09 14:09:08 +00:00
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_forwarded_setup_entry = AsyncMock(return_value=True)
|
2019-04-15 02:07:05 +00:00
|
|
|
mock_integration(
|
2019-07-31 19:25:30 +00:00
|
|
|
hass, MockModule("forwarded", async_setup_entry=mock_forwarded_setup_entry)
|
|
|
|
)
|
2018-04-09 14:09:08 +00:00
|
|
|
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
with patch.object(integration, "async_get_platforms") as mock_async_get_platforms:
|
2024-06-13 01:06:11 +00:00
|
|
|
await hass.config_entries.async_forward_entry_setups(entry, ["forwarded"])
|
2024-03-23 19:26:38 +00:00
|
|
|
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
mock_async_get_platforms.assert_called_once_with(["forwarded"])
|
2018-04-09 14:09:08 +00:00
|
|
|
assert len(mock_original_setup_entry.mock_calls) == 0
|
|
|
|
assert len(mock_forwarded_setup_entry.mock_calls) == 1
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_forward_entry_does_not_setup_entry_if_setup_fails(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
) -> None:
|
2018-08-19 20:29:08 +00:00
|
|
|
"""Test we do not set up entry if component setup fails."""
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="original", state=config_entries.ConfigEntryState.LOADED
|
|
|
|
)
|
|
|
|
|
|
|
|
mock_original_setup_entry = AsyncMock(return_value=True)
|
|
|
|
integration = mock_integration(
|
|
|
|
hass, MockModule("original", async_setup_entry=mock_original_setup_entry)
|
|
|
|
)
|
|
|
|
|
|
|
|
mock_setup = AsyncMock(return_value=False)
|
|
|
|
mock_setup_entry = AsyncMock()
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"forwarded", async_setup=mock_setup, async_setup_entry=mock_setup_entry
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
with patch.object(integration, "async_get_platforms"):
|
2024-06-13 01:06:11 +00:00
|
|
|
await hass.config_entries.async_forward_entry_setups(entry, ["forwarded"])
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
assert len(mock_setup.mock_calls) == 1
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 0
|
|
|
|
|
|
|
|
|
|
|
|
async def test_async_forward_entry_setup_deprecated(
|
|
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
|
|
|
) -> None:
|
|
|
|
"""Test async_forward_entry_setup is deprecated."""
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="original", state=config_entries.ConfigEntryState.LOADED
|
|
|
|
)
|
|
|
|
|
|
|
|
mock_original_setup_entry = AsyncMock(return_value=True)
|
|
|
|
integration = mock_integration(
|
|
|
|
hass, MockModule("original", async_setup_entry=mock_original_setup_entry)
|
|
|
|
)
|
2018-04-09 14:09:08 +00:00
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup = AsyncMock(return_value=False)
|
|
|
|
mock_setup_entry = AsyncMock()
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"forwarded", async_setup=mock_setup, async_setup_entry=mock_setup_entry
|
|
|
|
),
|
|
|
|
)
|
2018-04-09 14:09:08 +00:00
|
|
|
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
entry_id = entry.entry_id
|
|
|
|
caplog.clear()
|
|
|
|
with patch.object(integration, "async_get_platforms"):
|
|
|
|
async with entry.setup_lock:
|
|
|
|
await hass.config_entries.async_forward_entry_setup(entry, "forwarded")
|
|
|
|
|
|
|
|
assert (
|
|
|
|
"Detected code that calls async_forward_entry_setup for integration, "
|
|
|
|
f"original with title: Mock Title and entry_id: {entry_id}, "
|
|
|
|
"which is deprecated and will stop working in Home Assistant 2025.6, "
|
|
|
|
"await async_forward_entry_setups instead. Please report this issue."
|
|
|
|
) in caplog.text
|
2018-04-22 19:00:24 +00:00
|
|
|
|
|
|
|
|
2023-06-16 02:15:07 +00:00
|
|
|
async def test_discovery_notification(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2018-04-22 19:00:24 +00:00
|
|
|
"""Test that we create/dismiss a notification when source is discovery."""
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(hass, MockModule("test"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2018-04-22 19:00:24 +00:00
|
|
|
|
2019-08-20 17:46:51 +00:00
|
|
|
with patch.dict(config_entries.HANDLERS):
|
2018-04-22 19:00:24 +00:00
|
|
|
|
2019-08-20 17:46:51 +00:00
|
|
|
class TestFlow(config_entries.ConfigFlow, domain="test"):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
|
|
|
|
2019-08-20 17:46:51 +00:00
|
|
|
VERSION = 5
|
|
|
|
|
2020-06-15 11:38:38 +00:00
|
|
|
async def async_step_discovery(self, discovery_info):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test discovery step."""
|
2020-06-15 11:38:38 +00:00
|
|
|
return self.async_show_form(step_id="discovery_confirm")
|
|
|
|
|
|
|
|
async def async_step_discovery_confirm(self, discovery_info):
|
|
|
|
"""Test discovery confirm step."""
|
|
|
|
return self.async_create_entry(
|
|
|
|
title="Test Title", data={"token": "abcd"}
|
|
|
|
)
|
2018-04-22 19:00:24 +00:00
|
|
|
|
2023-05-26 03:09:13 +00:00
|
|
|
notifications = async_get_persistent_notifications(hass)
|
|
|
|
assert "config_entry_discovery" not in notifications
|
|
|
|
|
2020-10-15 20:46:27 +00:00
|
|
|
# Start first discovery flow to assert that reconfigure notification fires
|
|
|
|
flow1 = await hass.config_entries.flow.async_init(
|
2019-07-31 19:25:30 +00:00
|
|
|
"test", context={"source": config_entries.SOURCE_DISCOVERY}
|
|
|
|
)
|
2019-08-20 17:46:51 +00:00
|
|
|
await hass.async_block_till_done()
|
2023-05-26 03:09:13 +00:00
|
|
|
notifications = async_get_persistent_notifications(hass)
|
|
|
|
assert "config_entry_discovery" in notifications
|
2018-04-22 19:00:24 +00:00
|
|
|
|
2020-10-15 20:46:27 +00:00
|
|
|
# Start a second discovery flow so we can finish the first and assert that
|
|
|
|
# the discovery notification persists until the second one is complete
|
|
|
|
flow2 = await hass.config_entries.flow.async_init(
|
|
|
|
"test", context={"source": config_entries.SOURCE_DISCOVERY}
|
|
|
|
)
|
|
|
|
|
|
|
|
flow1 = await hass.config_entries.flow.async_configure(flow1["flow_id"], {})
|
2022-07-07 16:57:36 +00:00
|
|
|
assert flow1["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2020-10-15 20:46:27 +00:00
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
2023-05-26 03:09:13 +00:00
|
|
|
notifications = async_get_persistent_notifications(hass)
|
|
|
|
assert "config_entry_discovery" in notifications
|
2020-10-15 20:46:27 +00:00
|
|
|
|
|
|
|
flow2 = await hass.config_entries.flow.async_configure(flow2["flow_id"], {})
|
2022-07-07 16:57:36 +00:00
|
|
|
assert flow2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2018-04-22 19:00:24 +00:00
|
|
|
|
2019-08-20 17:46:51 +00:00
|
|
|
await hass.async_block_till_done()
|
2023-05-26 03:09:13 +00:00
|
|
|
notifications = async_get_persistent_notifications(hass)
|
|
|
|
assert "config_entry_discovery" not in notifications
|
2018-05-24 18:24:14 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_reauth_notification(hass: HomeAssistant) -> None:
|
2020-10-15 20:46:27 +00:00
|
|
|
"""Test that we create/dismiss a notification when source is reauth."""
|
|
|
|
mock_integration(hass, MockModule("test"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2020-10-15 20:46:27 +00:00
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS):
|
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow, domain="test"):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 5
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input):
|
|
|
|
"""Test user step."""
|
|
|
|
return self.async_show_form(step_id="user_confirm")
|
|
|
|
|
|
|
|
async def async_step_user_confirm(self, user_input):
|
|
|
|
"""Test user confirm step."""
|
|
|
|
return self.async_show_form(step_id="user_confirm")
|
|
|
|
|
|
|
|
async def async_step_reauth(self, user_input):
|
|
|
|
"""Test reauth step."""
|
|
|
|
return self.async_show_form(step_id="reauth_confirm")
|
|
|
|
|
|
|
|
async def async_step_reauth_confirm(self, user_input):
|
|
|
|
"""Test reauth confirm step."""
|
|
|
|
return self.async_abort(reason="test")
|
|
|
|
|
|
|
|
# Start user flow to assert that reconfigure notification doesn't fire
|
|
|
|
await hass.config_entries.flow.async_init(
|
|
|
|
"test", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
2023-05-26 03:09:13 +00:00
|
|
|
notifications = async_get_persistent_notifications(hass)
|
|
|
|
assert "config_entry_reconfigure" not in notifications
|
2020-10-15 20:46:27 +00:00
|
|
|
|
|
|
|
# Start first reauth flow to assert that reconfigure notification fires
|
|
|
|
flow1 = await hass.config_entries.flow.async_init(
|
|
|
|
"test", context={"source": config_entries.SOURCE_REAUTH}
|
|
|
|
)
|
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
2023-05-26 03:09:13 +00:00
|
|
|
notifications = async_get_persistent_notifications(hass)
|
|
|
|
assert "config_entry_reconfigure" in notifications
|
2020-10-15 20:46:27 +00:00
|
|
|
|
|
|
|
# Start a second reauth flow so we can finish the first and assert that
|
|
|
|
# the reconfigure notification persists until the second one is complete
|
|
|
|
flow2 = await hass.config_entries.flow.async_init(
|
|
|
|
"test", context={"source": config_entries.SOURCE_REAUTH}
|
|
|
|
)
|
|
|
|
|
|
|
|
flow1 = await hass.config_entries.flow.async_configure(flow1["flow_id"], {})
|
2022-07-07 16:57:36 +00:00
|
|
|
assert flow1["type"] == data_entry_flow.FlowResultType.ABORT
|
2020-10-15 20:46:27 +00:00
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
2023-05-26 03:09:13 +00:00
|
|
|
notifications = async_get_persistent_notifications(hass)
|
|
|
|
assert "config_entry_reconfigure" in notifications
|
2020-10-15 20:46:27 +00:00
|
|
|
|
|
|
|
flow2 = await hass.config_entries.flow.async_configure(flow2["flow_id"], {})
|
2022-07-07 16:57:36 +00:00
|
|
|
assert flow2["type"] == data_entry_flow.FlowResultType.ABORT
|
2020-10-15 20:46:27 +00:00
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
2023-05-26 03:09:13 +00:00
|
|
|
notifications = async_get_persistent_notifications(hass)
|
|
|
|
assert "config_entry_reconfigure" not in notifications
|
2020-10-15 20:46:27 +00:00
|
|
|
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
async def test_reauth_issue(
|
2024-05-24 13:26:32 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
issue_registry: ir.IssueRegistry,
|
2024-05-11 23:20:08 +00:00
|
|
|
) -> None:
|
2024-01-31 14:05:52 +00:00
|
|
|
"""Test that we create/delete an issue when source is reauth."""
|
|
|
|
assert len(issue_registry.issues) == 0
|
|
|
|
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(side_effect=ConfigEntryAuthFailed())
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
|
|
|
mock_platform(hass, "test.config_flow", None)
|
|
|
|
|
|
|
|
entry.add_to_hass(hass)
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2024-01-31 14:05:52 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
flows = hass.config_entries.flow.async_progress_by_handler("test")
|
|
|
|
assert len(flows) == 1
|
|
|
|
|
|
|
|
assert len(issue_registry.issues) == 1
|
|
|
|
issue_id = f"config_entry_reauth_test_{entry.entry_id}"
|
|
|
|
issue = issue_registry.async_get_issue(HA_DOMAIN, issue_id)
|
|
|
|
assert issue == ir.IssueEntry(
|
|
|
|
active=True,
|
|
|
|
breaks_in_ha_version=None,
|
|
|
|
created=ANY,
|
|
|
|
data={"flow_id": flows[0]["flow_id"]},
|
|
|
|
dismissed_version=None,
|
|
|
|
domain=HA_DOMAIN,
|
|
|
|
is_fixable=False,
|
|
|
|
is_persistent=False,
|
|
|
|
issue_domain="test",
|
|
|
|
issue_id=issue_id,
|
|
|
|
learn_more_url=None,
|
|
|
|
severity=ir.IssueSeverity.ERROR,
|
|
|
|
translation_key="config_entry_reauth",
|
2024-02-26 13:19:37 +00:00
|
|
|
translation_placeholders={"name": "test_title"},
|
2024-01-31 14:05:52 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
result = await hass.config_entries.flow.async_configure(issue.data["flow_id"], {})
|
|
|
|
assert result["type"] == FlowResultType.ABORT
|
|
|
|
assert len(issue_registry.issues) == 0
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_discovery_notification_not_created(hass: HomeAssistant) -> None:
|
2018-05-24 18:24:14 +00:00
|
|
|
"""Test that we not create a notification when discovery is aborted."""
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(hass, MockModule("test"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2018-05-24 18:24:14 +00:00
|
|
|
|
2018-09-17 08:12:46 +00:00
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
|
|
|
|
2018-05-24 18:24:14 +00:00
|
|
|
VERSION = 5
|
|
|
|
|
2020-06-15 11:38:38 +00:00
|
|
|
async def async_step_discovery(self, discovery_info):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test discovery step."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return self.async_abort(reason="test")
|
2018-05-24 18:24:14 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch.dict(config_entries.HANDLERS, {"test": TestFlow}):
|
2018-05-24 18:24:14 +00:00
|
|
|
await hass.config_entries.flow.async_init(
|
2019-07-31 19:25:30 +00:00
|
|
|
"test", context={"source": config_entries.SOURCE_DISCOVERY}
|
|
|
|
)
|
2018-05-24 18:24:14 +00:00
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
2019-07-31 19:25:30 +00:00
|
|
|
state = hass.states.get("persistent_notification.config_entry_discovery")
|
2018-05-24 18:24:14 +00:00
|
|
|
assert state is None
|
2018-06-25 21:21:38 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_loading_default_config(hass: HomeAssistant) -> None:
|
2018-06-25 21:21:38 +00:00
|
|
|
"""Test loading the default config."""
|
|
|
|
manager = config_entries.ConfigEntries(hass, {})
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
with patch("homeassistant.util.json.open", side_effect=FileNotFoundError):
|
2019-03-01 04:27:20 +00:00
|
|
|
await manager.async_initialize()
|
2018-06-25 21:21:38 +00:00
|
|
|
|
|
|
|
assert len(manager.async_entries()) == 0
|
2018-09-25 10:21:11 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_updating_entry_data(manager: config_entries.ConfigEntries) -> None:
|
2018-09-25 10:21:11 +00:00
|
|
|
"""Test that we can update an entry data."""
|
|
|
|
entry = MockConfigEntry(
|
2019-07-31 19:25:30 +00:00
|
|
|
domain="test",
|
|
|
|
data={"first": True},
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.SETUP_ERROR,
|
2018-09-25 10:21:11 +00:00
|
|
|
)
|
|
|
|
entry.add_to_manager(manager)
|
|
|
|
|
2020-08-08 18:23:56 +00:00
|
|
|
assert manager.async_update_entry(entry) is False
|
2019-07-31 19:25:30 +00:00
|
|
|
assert entry.data == {"first": True}
|
2019-02-14 04:36:06 +00:00
|
|
|
|
2020-08-08 18:23:56 +00:00
|
|
|
assert manager.async_update_entry(entry, data={"second": True}) is True
|
2019-07-31 19:25:30 +00:00
|
|
|
assert entry.data == {"second": True}
|
2018-10-04 13:53:50 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_updating_entry_system_options(
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
) -> None:
|
2019-08-18 04:34:11 +00:00
|
|
|
"""Test that we can update an entry data."""
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="test",
|
|
|
|
data={"first": True},
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.SETUP_ERROR,
|
2021-06-01 20:34:31 +00:00
|
|
|
pref_disable_new_entities=True,
|
2019-08-18 04:34:11 +00:00
|
|
|
)
|
|
|
|
entry.add_to_manager(manager)
|
|
|
|
|
2021-06-01 20:34:31 +00:00
|
|
|
assert entry.pref_disable_new_entities is True
|
|
|
|
assert entry.pref_disable_polling is False
|
|
|
|
|
|
|
|
manager.async_update_entry(
|
|
|
|
entry, pref_disable_new_entities=False, pref_disable_polling=True
|
|
|
|
)
|
2019-08-18 04:34:11 +00:00
|
|
|
|
2021-06-01 20:34:31 +00:00
|
|
|
assert entry.pref_disable_new_entities is False
|
|
|
|
assert entry.pref_disable_polling is True
|
2019-08-18 04:34:11 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_update_entry_options_and_trigger_listener(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2019-02-22 16:59:43 +00:00
|
|
|
"""Test that we can update entry options and trigger listener."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entry = MockConfigEntry(domain="test", options={"first": True})
|
2019-02-22 16:59:43 +00:00
|
|
|
entry.add_to_manager(manager)
|
2024-05-11 19:45:03 +00:00
|
|
|
update_listener_calls = []
|
2019-02-22 16:59:43 +00:00
|
|
|
|
|
|
|
async def update_listener(hass, entry):
|
|
|
|
"""Test function."""
|
2019-07-31 19:25:30 +00:00
|
|
|
assert entry.options == {"second": True}
|
2024-05-11 19:45:03 +00:00
|
|
|
update_listener_calls.append(None)
|
2019-02-22 16:59:43 +00:00
|
|
|
|
|
|
|
entry.add_update_listener(update_listener)
|
|
|
|
|
2020-08-08 18:23:56 +00:00
|
|
|
assert manager.async_update_entry(entry, options={"second": True}) is True
|
2019-02-22 16:59:43 +00:00
|
|
|
|
2024-05-11 19:45:03 +00:00
|
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
2019-07-31 19:25:30 +00:00
|
|
|
assert entry.options == {"second": True}
|
2024-05-11 19:45:03 +00:00
|
|
|
assert len(update_listener_calls) == 1
|
2019-02-22 16:59:43 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_setup_raise_not_ready(
|
2024-05-11 23:20:08 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2018-10-04 13:53:50 +00:00
|
|
|
"""Test a setup raising not ready."""
|
2021-01-22 17:13:23 +00:00
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2018-10-04 13:53:50 +00:00
|
|
|
|
2021-03-29 10:25:40 +00:00
|
|
|
mock_setup_entry = AsyncMock(
|
|
|
|
side_effect=ConfigEntryNotReady("The internet connection is offline")
|
|
|
|
)
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2018-10-04 13:53:50 +00:00
|
|
|
|
2022-01-11 21:30:59 +00:00
|
|
|
with patch("homeassistant.config_entries.async_call_later") as mock_call:
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2018-10-04 13:53:50 +00:00
|
|
|
|
|
|
|
assert len(mock_call.mock_calls) == 1
|
2021-03-29 10:25:40 +00:00
|
|
|
assert (
|
2023-01-20 12:52:46 +00:00
|
|
|
"Config entry 'test_title' for test integration not ready yet:"
|
|
|
|
" The internet connection is offline"
|
|
|
|
) in caplog.text
|
|
|
|
|
2018-10-04 13:53:50 +00:00
|
|
|
p_hass, p_wait_time, p_setup = mock_call.mock_calls[0][1]
|
|
|
|
|
|
|
|
assert p_hass is hass
|
2022-12-28 01:59:42 +00:00
|
|
|
assert 5 <= p_wait_time <= 5.5
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
|
2021-04-23 07:23:43 +00:00
|
|
|
assert entry.reason == "The internet connection is offline"
|
2018-10-04 13:53:50 +00:00
|
|
|
|
|
|
|
mock_setup_entry.side_effect = None
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup_entry.return_value = True
|
2018-10-04 13:53:50 +00:00
|
|
|
|
2024-02-29 14:47:36 +00:00
|
|
|
hass.async_run_hass_job(p_setup, None)
|
|
|
|
await hass.async_block_till_done()
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2021-04-23 07:23:43 +00:00
|
|
|
assert entry.reason is None
|
2018-10-04 13:53:50 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_setup_raise_not_ready_from_exception(
|
2024-05-11 23:20:08 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-03-29 10:25:40 +00:00
|
|
|
"""Test a setup raising not ready from another exception."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2021-03-29 10:25:40 +00:00
|
|
|
|
|
|
|
original_exception = HomeAssistantError("The device dropped the connection")
|
|
|
|
config_entry_exception = ConfigEntryNotReady()
|
|
|
|
config_entry_exception.__cause__ = original_exception
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(side_effect=config_entry_exception)
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2021-03-29 10:25:40 +00:00
|
|
|
|
2022-01-11 21:30:59 +00:00
|
|
|
with patch("homeassistant.config_entries.async_call_later") as mock_call:
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2021-03-29 10:25:40 +00:00
|
|
|
|
|
|
|
assert len(mock_call.mock_calls) == 1
|
|
|
|
assert (
|
2023-01-20 12:52:46 +00:00
|
|
|
"Config entry 'test_title' for test integration not ready yet: The device"
|
|
|
|
" dropped the connection" in caplog.text
|
2021-03-29 10:25:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
async def test_setup_retrying_during_unload(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2018-10-04 13:53:50 +00:00
|
|
|
"""Test if we unload an entry that is in retry mode."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entry = MockConfigEntry(domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2018-10-04 13:53:50 +00:00
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup_entry = AsyncMock(side_effect=ConfigEntryNotReady)
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2018-10-04 13:53:50 +00:00
|
|
|
|
2022-01-11 21:30:59 +00:00
|
|
|
with patch("homeassistant.config_entries.async_call_later") as mock_call:
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2018-10-04 13:53:50 +00:00
|
|
|
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
|
2018-10-04 13:53:50 +00:00
|
|
|
assert len(mock_call.return_value.mock_calls) == 0
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_unload(entry.entry_id)
|
2018-10-04 13:53:50 +00:00
|
|
|
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
2018-10-04 13:53:50 +00:00
|
|
|
assert len(mock_call.return_value.mock_calls) == 1
|
2019-02-22 16:59:43 +00:00
|
|
|
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
async def test_setup_retrying_during_unload_before_started(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2021-04-04 00:00:22 +00:00
|
|
|
"""Test if we unload an entry that is in retry mode before started."""
|
|
|
|
entry = MockConfigEntry(domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2024-01-18 18:41:32 +00:00
|
|
|
hass.set_state(CoreState.starting)
|
2021-04-04 00:00:22 +00:00
|
|
|
initial_listeners = hass.bus.async_listeners()[EVENT_HOMEASSISTANT_STARTED]
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(side_effect=ConfigEntryNotReady)
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2021-04-04 00:00:22 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2021-04-04 00:00:22 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
|
2021-04-04 00:00:22 +00:00
|
|
|
assert (
|
|
|
|
hass.bus.async_listeners()[EVENT_HOMEASSISTANT_STARTED] == initial_listeners + 1
|
|
|
|
)
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_unload(entry.entry_id)
|
2021-04-04 00:00:22 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
2021-04-04 00:00:22 +00:00
|
|
|
assert (
|
|
|
|
hass.bus.async_listeners()[EVENT_HOMEASSISTANT_STARTED] == initial_listeners + 0
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
async def test_setup_does_not_retry_during_shutdown(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2023-02-27 03:01:02 +00:00
|
|
|
"""Test we do not retry when HASS is shutting down."""
|
|
|
|
entry = MockConfigEntry(domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2023-02-27 03:01:02 +00:00
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(side_effect=ConfigEntryNotReady)
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2023-02-27 03:01:02 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2023-02-27 03:01:02 +00:00
|
|
|
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
2024-01-18 18:41:32 +00:00
|
|
|
hass.set_state(CoreState.stopping)
|
2023-02-27 03:01:02 +00:00
|
|
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
|
|
|
|
|
2024-04-13 20:26:41 +00:00
|
|
|
async def test_reload_during_setup_retrying_waits(hass: HomeAssistant) -> None:
|
|
|
|
"""Test reloading during setup retry waits."""
|
|
|
|
entry = MockConfigEntry(domain="test")
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
load_attempts = []
|
|
|
|
sleep_duration = 0
|
|
|
|
|
|
|
|
async def _mock_setup_entry(hass, entry):
|
|
|
|
"""Mock setup entry."""
|
|
|
|
nonlocal sleep_duration
|
|
|
|
await asyncio.sleep(sleep_duration)
|
|
|
|
load_attempts.append(entry.entry_id)
|
|
|
|
raise ConfigEntryNotReady
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=_mock_setup_entry))
|
|
|
|
mock_platform(hass, "test.config_flow", None)
|
|
|
|
|
|
|
|
await hass.async_create_task(
|
|
|
|
hass.config_entries.async_setup(entry.entry_id), eager_start=True
|
|
|
|
)
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
|
|
|
|
|
|
|
|
# Now make the setup take a while so that the setup retry
|
|
|
|
# will still be in progress when the reload request comes in
|
|
|
|
sleep_duration = 0.1
|
|
|
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
|
|
|
|
# Should not raise homeassistant.config_entries.OperationNotAllowed
|
|
|
|
await hass.config_entries.async_reload(entry.entry_id)
|
|
|
|
|
|
|
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
|
|
|
|
# Should not raise homeassistant.config_entries.OperationNotAllowed
|
|
|
|
hass.config_entries.async_schedule_reload(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert load_attempts == [
|
|
|
|
entry.entry_id,
|
|
|
|
entry.entry_id,
|
|
|
|
entry.entry_id,
|
|
|
|
entry.entry_id,
|
|
|
|
entry.entry_id,
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2023-06-16 02:15:07 +00:00
|
|
|
async def test_create_entry_options(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2021-05-06 05:14:01 +00:00
|
|
|
"""Test a config entry being created with options."""
|
|
|
|
|
|
|
|
async def mock_async_setup(hass, config):
|
|
|
|
"""Mock setup."""
|
|
|
|
hass.async_create_task(
|
|
|
|
hass.config_entries.flow.async_init(
|
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_IMPORT},
|
|
|
|
data={"data": "data", "option": "option"},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp", async_setup=mock_async_setup, async_setup_entry=async_setup_entry
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2021-05-06 05:14:01 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_import(self, user_input):
|
|
|
|
"""Test import step creating entry, with options."""
|
|
|
|
return self.async_create_entry(
|
|
|
|
title="title",
|
|
|
|
data={"example": user_input["data"]},
|
|
|
|
options={"example": user_input["option"]},
|
|
|
|
)
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
assert await async_setup_component(hass, "comp", {})
|
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(async_setup_entry.mock_calls) == 1
|
|
|
|
|
|
|
|
entries = hass.config_entries.async_entries("comp")
|
|
|
|
assert len(entries) == 1
|
2024-01-26 06:20:19 +00:00
|
|
|
assert entries[0].supports_options is False
|
2021-05-06 05:14:01 +00:00
|
|
|
assert entries[0].data == {"example": "data"}
|
|
|
|
assert entries[0].options == {"example": "option"}
|
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_options(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-02-22 16:59:43 +00:00
|
|
|
"""Test that we can set options on an entry."""
|
2023-03-22 19:10:10 +00:00
|
|
|
mock_integration(hass, MockModule("test"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2019-07-31 19:25:30 +00:00
|
|
|
entry = MockConfigEntry(domain="test", data={"first": True}, options=None)
|
2019-02-22 16:59:43 +00:00
|
|
|
entry.add_to_manager(manager)
|
|
|
|
|
2024-05-11 18:11:18 +00:00
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
|
|
|
|
2019-02-22 16:59:43 +00:00
|
|
|
@staticmethod
|
|
|
|
@callback
|
2019-08-15 21:11:55 +00:00
|
|
|
def async_get_options_flow(config_entry):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test options flow."""
|
|
|
|
|
2019-02-22 16:59:43 +00:00
|
|
|
class OptionsFlowHandler(data_entry_flow.FlowHandler):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test options flow handler."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2019-08-15 21:11:55 +00:00
|
|
|
return OptionsFlowHandler()
|
2019-02-22 16:59:43 +00:00
|
|
|
|
2024-05-11 18:11:18 +00:00
|
|
|
with mock_config_flow("test", TestFlow):
|
|
|
|
flow = await manager.options.async_create_flow(
|
|
|
|
entry.entry_id, context={"source": "test"}, data=None
|
|
|
|
)
|
2019-02-22 16:59:43 +00:00
|
|
|
|
2024-05-11 18:11:18 +00:00
|
|
|
flow.handler = entry.entry_id # Used to keep reference to config entry
|
2019-02-22 16:59:43 +00:00
|
|
|
|
2024-05-11 18:11:18 +00:00
|
|
|
await manager.options.async_finish_flow(
|
|
|
|
flow,
|
|
|
|
{
|
|
|
|
"data": {"second": True},
|
|
|
|
"type": data_entry_flow.FlowResultType.CREATE_ENTRY,
|
|
|
|
},
|
|
|
|
)
|
2019-02-22 16:59:43 +00:00
|
|
|
|
2024-05-11 18:11:18 +00:00
|
|
|
assert entry.data == {"first": True}
|
|
|
|
assert entry.options == {"second": True}
|
|
|
|
assert entry.supports_options is True
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_options_abort(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2020-11-09 07:59:42 +00:00
|
|
|
"""Test that we can abort options flow."""
|
2023-03-22 19:10:10 +00:00
|
|
|
mock_integration(hass, MockModule("test"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2020-11-09 07:59:42 +00:00
|
|
|
entry = MockConfigEntry(domain="test", data={"first": True}, options=None)
|
|
|
|
entry.add_to_manager(manager)
|
|
|
|
|
2024-05-11 18:11:18 +00:00
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-11-09 07:59:42 +00:00
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@callback
|
|
|
|
def async_get_options_flow(config_entry):
|
|
|
|
"""Test options flow."""
|
|
|
|
|
|
|
|
class OptionsFlowHandler(data_entry_flow.FlowHandler):
|
|
|
|
"""Test options flow handler."""
|
|
|
|
|
|
|
|
return OptionsFlowHandler()
|
|
|
|
|
2024-05-11 18:11:18 +00:00
|
|
|
with mock_config_flow("test", TestFlow):
|
|
|
|
flow = await manager.options.async_create_flow(
|
|
|
|
entry.entry_id, context={"source": "test"}, data=None
|
|
|
|
)
|
2020-11-09 07:59:42 +00:00
|
|
|
|
2024-05-11 18:11:18 +00:00
|
|
|
flow.handler = entry.entry_id # Used to keep reference to config entry
|
2020-11-09 07:59:42 +00:00
|
|
|
|
2024-05-11 18:11:18 +00:00
|
|
|
assert await manager.options.async_finish_flow(
|
|
|
|
flow, {"type": data_entry_flow.FlowResultType.ABORT, "reason": "test"}
|
|
|
|
)
|
2020-11-09 07:59:42 +00:00
|
|
|
|
|
|
|
|
2023-08-22 08:29:16 +00:00
|
|
|
async def test_entry_options_unknown_config_entry(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
|
|
|
"""Test that we can abort options flow."""
|
|
|
|
mock_integration(hass, MockModule("test"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2023-08-22 08:29:16 +00:00
|
|
|
|
|
|
|
class TestFlow:
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@callback
|
|
|
|
def async_get_options_flow(config_entry):
|
|
|
|
"""Test options flow."""
|
|
|
|
|
|
|
|
with pytest.raises(config_entries.UnknownEntry):
|
|
|
|
await manager.options.async_create_flow(
|
|
|
|
"blah", context={"source": "test"}, data=None
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_setup_succeed(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-03-01 04:27:20 +00:00
|
|
|
"""Test that we can setup an entry."""
|
2021-05-20 17:19:20 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", state=config_entries.ConfigEntryState.NOT_LOADED
|
|
|
|
)
|
2019-03-01 04:27:20 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup = AsyncMock(return_value=True)
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule("comp", async_setup=mock_setup, async_setup_entry=mock_setup_entry),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
assert await manager.async_setup(entry.entry_id)
|
|
|
|
assert len(mock_setup.mock_calls) == 1
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"state",
|
2024-03-19 08:01:07 +00:00
|
|
|
[
|
2021-05-20 17:19:20 +00:00
|
|
|
config_entries.ConfigEntryState.LOADED,
|
|
|
|
config_entries.ConfigEntryState.SETUP_ERROR,
|
|
|
|
config_entries.ConfigEntryState.MIGRATION_ERROR,
|
|
|
|
config_entries.ConfigEntryState.SETUP_RETRY,
|
|
|
|
config_entries.ConfigEntryState.FAILED_UNLOAD,
|
2024-03-19 08:01:07 +00:00
|
|
|
],
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_setup_invalid_state(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
state: config_entries.ConfigEntryState,
|
|
|
|
) -> None:
|
2019-03-01 04:27:20 +00:00
|
|
|
"""Test that we cannot setup an entry with invalid state."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entry = MockConfigEntry(domain="comp", state=state)
|
2019-03-01 04:27:20 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup = AsyncMock(return_value=True)
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule("comp", async_setup=mock_setup, async_setup_entry=mock_setup_entry),
|
|
|
|
)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2022-09-17 17:52:28 +00:00
|
|
|
with pytest.raises(config_entries.OperationNotAllowed, match=str(state)):
|
2019-03-01 04:27:20 +00:00
|
|
|
assert await manager.async_setup(entry.entry_id)
|
|
|
|
|
|
|
|
assert len(mock_setup.mock_calls) == 0
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 0
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is state
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_unload_succeed(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-03-01 04:27:20 +00:00
|
|
|
"""Test that we can unload an entry."""
|
2024-06-11 02:11:07 +00:00
|
|
|
unloads_called = []
|
|
|
|
|
|
|
|
async def verify_runtime_data(*args):
|
|
|
|
"""Verify runtime data."""
|
|
|
|
assert entry.runtime_data == 2
|
|
|
|
unloads_called.append(args)
|
|
|
|
return True
|
|
|
|
|
2021-05-20 17:19:20 +00:00
|
|
|
entry = MockConfigEntry(domain="comp", state=config_entries.ConfigEntryState.LOADED)
|
2019-03-01 04:27:20 +00:00
|
|
|
entry.add_to_hass(hass)
|
2024-06-11 02:11:07 +00:00
|
|
|
entry.async_on_unload(verify_runtime_data)
|
2024-05-12 17:53:22 +00:00
|
|
|
entry.runtime_data = 2
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2024-06-11 02:11:07 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_unload_entry=verify_runtime_data))
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
assert await manager.async_unload(entry.entry_id)
|
2024-06-11 02:11:07 +00:00
|
|
|
assert len(unloads_called) == 2
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
2024-05-12 17:53:22 +00:00
|
|
|
assert not hasattr(entry, "runtime_data")
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"state",
|
2024-03-19 08:01:07 +00:00
|
|
|
[
|
2021-05-20 17:19:20 +00:00
|
|
|
config_entries.ConfigEntryState.NOT_LOADED,
|
|
|
|
config_entries.ConfigEntryState.SETUP_ERROR,
|
|
|
|
config_entries.ConfigEntryState.SETUP_RETRY,
|
2024-03-19 08:01:07 +00:00
|
|
|
],
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_unload_failed_to_load(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
state: config_entries.ConfigEntryState,
|
|
|
|
) -> None:
|
2019-03-01 04:27:20 +00:00
|
|
|
"""Test that we can unload an entry."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entry = MockConfigEntry(domain="comp", state=state)
|
2019-03-01 04:27:20 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_unload_entry=async_unload_entry))
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
assert await manager.async_unload(entry.entry_id)
|
|
|
|
assert len(async_unload_entry.mock_calls) == 0
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"state",
|
2024-03-19 08:01:07 +00:00
|
|
|
[
|
2021-05-20 17:19:20 +00:00
|
|
|
config_entries.ConfigEntryState.MIGRATION_ERROR,
|
|
|
|
config_entries.ConfigEntryState.FAILED_UNLOAD,
|
2024-03-19 08:01:07 +00:00
|
|
|
],
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_unload_invalid_state(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
state: config_entries.ConfigEntryState,
|
|
|
|
) -> None:
|
2019-03-01 04:27:20 +00:00
|
|
|
"""Test that we cannot unload an entry with invalid state."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entry = MockConfigEntry(domain="comp", state=state)
|
2019-03-01 04:27:20 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_unload_entry=async_unload_entry))
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2022-09-17 17:52:28 +00:00
|
|
|
with pytest.raises(config_entries.OperationNotAllowed, match=str(state)):
|
2019-03-01 04:27:20 +00:00
|
|
|
assert await manager.async_unload(entry.entry_id)
|
|
|
|
|
|
|
|
assert len(async_unload_entry.mock_calls) == 0
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is state
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_reload_succeed(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-03-01 04:27:20 +00:00
|
|
|
"""Test that we can reload an entry."""
|
2024-05-10 22:09:28 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", state=config_entries.ConfigEntryState.NOT_LOADED
|
|
|
|
)
|
2019-03-01 04:27:20 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
assert await manager.async_reload(entry.entry_id)
|
|
|
|
assert len(async_setup.mock_calls) == 1
|
|
|
|
assert len(async_setup_entry.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
|
2024-05-10 22:09:28 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"state",
|
|
|
|
[
|
|
|
|
config_entries.ConfigEntryState.LOADED,
|
|
|
|
config_entries.ConfigEntryState.SETUP_IN_PROGRESS,
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_entry_cannot_be_loaded_twice(
|
|
|
|
hass: HomeAssistant, state: config_entries.ConfigEntryState
|
|
|
|
) -> None:
|
|
|
|
"""Test that a config entry cannot be loaded twice."""
|
|
|
|
entry = MockConfigEntry(domain="comp", state=state)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
|
|
|
|
with pytest.raises(config_entries.OperationNotAllowed, match=str(state)):
|
|
|
|
await entry.async_setup(hass)
|
|
|
|
assert len(async_setup.mock_calls) == 0
|
|
|
|
assert len(async_setup_entry.mock_calls) == 0
|
|
|
|
assert entry.state is state
|
|
|
|
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
async def test_entry_setup_without_lock_raises(hass: HomeAssistant) -> None:
|
|
|
|
"""Test trying to setup a config entry without the lock."""
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", state=config_entries.ConfigEntryState.NOT_LOADED
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
|
|
|
|
with pytest.raises(
|
|
|
|
config_entries.OperationNotAllowed,
|
|
|
|
match="cannot be set up because it does not hold the setup lock",
|
|
|
|
):
|
|
|
|
await entry.async_setup(hass)
|
|
|
|
assert len(async_setup.mock_calls) == 0
|
|
|
|
assert len(async_setup_entry.mock_calls) == 0
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
|
|
|
|
|
|
|
|
|
|
|
async def test_entry_unload_without_lock_raises(hass: HomeAssistant) -> None:
|
|
|
|
"""Test trying to unload a config entry without the lock."""
|
|
|
|
entry = MockConfigEntry(domain="comp", state=config_entries.ConfigEntryState.LOADED)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
|
|
|
|
with pytest.raises(
|
|
|
|
config_entries.OperationNotAllowed,
|
|
|
|
match="cannot be unloaded because it does not hold the setup lock",
|
|
|
|
):
|
|
|
|
await entry.async_unload(hass)
|
|
|
|
assert len(async_setup.mock_calls) == 0
|
|
|
|
assert len(async_setup_entry.mock_calls) == 0
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
|
|
|
|
|
|
|
|
|
|
|
async def test_entry_remove_without_lock_raises(hass: HomeAssistant) -> None:
|
|
|
|
"""Test trying to remove a config entry without the lock."""
|
|
|
|
entry = MockConfigEntry(domain="comp", state=config_entries.ConfigEntryState.LOADED)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
|
|
|
|
with pytest.raises(
|
|
|
|
config_entries.OperationNotAllowed,
|
|
|
|
match="cannot be removed because it does not hold the setup lock",
|
|
|
|
):
|
|
|
|
await entry.async_remove(hass)
|
|
|
|
assert len(async_setup.mock_calls) == 0
|
|
|
|
assert len(async_setup_entry.mock_calls) == 0
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"state",
|
2024-03-19 08:01:07 +00:00
|
|
|
[
|
2021-05-20 17:19:20 +00:00
|
|
|
config_entries.ConfigEntryState.NOT_LOADED,
|
|
|
|
config_entries.ConfigEntryState.SETUP_ERROR,
|
|
|
|
config_entries.ConfigEntryState.SETUP_RETRY,
|
2024-03-19 08:01:07 +00:00
|
|
|
],
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_reload_not_loaded(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
state: config_entries.ConfigEntryState,
|
|
|
|
) -> None:
|
2019-03-01 04:27:20 +00:00
|
|
|
"""Test that we can reload an entry."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entry = MockConfigEntry(domain="comp", state=state)
|
2019-03-01 04:27:20 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
assert await manager.async_reload(entry.entry_id)
|
|
|
|
assert len(async_unload_entry.mock_calls) == 0
|
|
|
|
assert len(async_setup.mock_calls) == 1
|
|
|
|
assert len(async_setup_entry.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2019-03-01 04:27:20 +00:00
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"state",
|
2024-03-19 08:01:07 +00:00
|
|
|
[
|
2021-05-20 17:19:20 +00:00
|
|
|
config_entries.ConfigEntryState.MIGRATION_ERROR,
|
|
|
|
config_entries.ConfigEntryState.FAILED_UNLOAD,
|
2024-03-19 08:01:07 +00:00
|
|
|
],
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_reload_error(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
state: config_entries.ConfigEntryState,
|
|
|
|
) -> None:
|
2019-03-01 04:27:20 +00:00
|
|
|
"""Test that we can reload an entry."""
|
2019-07-31 19:25:30 +00:00
|
|
|
entry = MockConfigEntry(domain="comp", state=state)
|
2019-03-01 04:27:20 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
2019-03-01 04:27:20 +00:00
|
|
|
|
2024-04-30 23:47:12 +00:00
|
|
|
hass.config.components.add("comp")
|
|
|
|
|
2022-09-17 17:52:28 +00:00
|
|
|
with pytest.raises(config_entries.OperationNotAllowed, match=str(state)):
|
2019-03-01 04:27:20 +00:00
|
|
|
assert await manager.async_reload(entry.entry_id)
|
|
|
|
|
|
|
|
assert len(async_unload_entry.mock_calls) == 0
|
|
|
|
assert len(async_setup.mock_calls) == 0
|
|
|
|
assert len(async_setup_entry.mock_calls) == 0
|
|
|
|
|
|
|
|
assert entry.state == state
|
2019-06-20 20:22:12 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_disable_succeed(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2021-02-21 03:21:39 +00:00
|
|
|
"""Test that we can disable an entry."""
|
2021-05-20 17:19:20 +00:00
|
|
|
entry = MockConfigEntry(domain="comp", state=config_entries.ConfigEntryState.LOADED)
|
2021-02-21 03:21:39 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2024-04-30 23:47:12 +00:00
|
|
|
hass.config.components.add("comp")
|
2021-02-21 03:21:39 +00:00
|
|
|
|
|
|
|
# Disable
|
2024-04-30 23:47:12 +00:00
|
|
|
assert len(async_setup.mock_calls) == 0
|
|
|
|
assert len(async_setup_entry.mock_calls) == 0
|
2021-02-21 03:21:39 +00:00
|
|
|
assert await manager.async_set_disabled_by(
|
2021-12-15 19:53:21 +00:00
|
|
|
entry.entry_id, config_entries.ConfigEntryDisabler.USER
|
2021-02-21 03:21:39 +00:00
|
|
|
)
|
|
|
|
assert len(async_unload_entry.mock_calls) == 1
|
|
|
|
assert len(async_setup.mock_calls) == 0
|
|
|
|
assert len(async_setup_entry.mock_calls) == 0
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
2021-02-21 03:21:39 +00:00
|
|
|
|
|
|
|
# Enable
|
|
|
|
assert await manager.async_set_disabled_by(entry.entry_id, None)
|
|
|
|
assert len(async_unload_entry.mock_calls) == 1
|
2024-04-30 23:47:12 +00:00
|
|
|
assert len(async_setup.mock_calls) == 0
|
2021-02-21 03:21:39 +00:00
|
|
|
assert len(async_setup_entry.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2021-02-21 03:21:39 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_entry_disable_without_reload_support(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-02-21 03:21:39 +00:00
|
|
|
"""Test that we can disable an entry without reload support."""
|
2021-05-20 17:19:20 +00:00
|
|
|
entry = MockConfigEntry(domain="comp", state=config_entries.ConfigEntryState.LOADED)
|
2021-02-21 03:21:39 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2024-04-30 23:47:12 +00:00
|
|
|
hass.config.components.add("comp")
|
2021-02-21 03:21:39 +00:00
|
|
|
|
|
|
|
# Disable
|
|
|
|
assert not await manager.async_set_disabled_by(
|
2021-12-15 19:53:21 +00:00
|
|
|
entry.entry_id, config_entries.ConfigEntryDisabler.USER
|
2021-02-21 03:21:39 +00:00
|
|
|
)
|
|
|
|
assert len(async_setup.mock_calls) == 0
|
|
|
|
assert len(async_setup_entry.mock_calls) == 0
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.FAILED_UNLOAD
|
2021-02-21 03:21:39 +00:00
|
|
|
|
|
|
|
# Enable
|
2022-09-17 17:52:28 +00:00
|
|
|
with pytest.raises(
|
|
|
|
config_entries.OperationNotAllowed,
|
|
|
|
match=str(config_entries.ConfigEntryState.FAILED_UNLOAD),
|
|
|
|
):
|
2021-02-21 03:21:39 +00:00
|
|
|
await manager.async_set_disabled_by(entry.entry_id, None)
|
|
|
|
assert len(async_setup.mock_calls) == 0
|
|
|
|
assert len(async_setup_entry.mock_calls) == 0
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.FAILED_UNLOAD
|
2021-02-21 03:21:39 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_entry_enable_without_reload_support(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-02-21 03:21:39 +00:00
|
|
|
"""Test that we can disable an entry without reload support."""
|
2021-12-15 19:53:21 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", disabled_by=config_entries.ConfigEntryDisabler.USER
|
|
|
|
)
|
2021-02-21 03:21:39 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2021-02-21 03:21:39 +00:00
|
|
|
|
|
|
|
# Enable
|
|
|
|
assert await manager.async_set_disabled_by(entry.entry_id, None)
|
|
|
|
assert len(async_setup.mock_calls) == 1
|
|
|
|
assert len(async_setup_entry.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2021-02-21 03:21:39 +00:00
|
|
|
|
|
|
|
# Disable
|
|
|
|
assert not await manager.async_set_disabled_by(
|
2021-12-15 19:53:21 +00:00
|
|
|
entry.entry_id, config_entries.ConfigEntryDisabler.USER
|
2021-02-21 03:21:39 +00:00
|
|
|
)
|
|
|
|
assert len(async_setup.mock_calls) == 1
|
|
|
|
assert len(async_setup_entry.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.FAILED_UNLOAD
|
2021-02-21 03:21:39 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_init_custom_integration(hass: HomeAssistant) -> None:
|
2019-06-20 20:22:12 +00:00
|
|
|
"""Test initializing flow for custom integration."""
|
2019-07-31 19:25:30 +00:00
|
|
|
integration = loader.Integration(
|
|
|
|
hass,
|
|
|
|
"custom_components.hue",
|
|
|
|
None,
|
|
|
|
{"name": "Hue", "dependencies": [], "requirements": [], "domain": "hue"},
|
|
|
|
)
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
pytest.raises(data_entry_flow.UnknownHandler),
|
|
|
|
patch(
|
|
|
|
"homeassistant.loader.async_get_integration",
|
|
|
|
return_value=integration,
|
|
|
|
),
|
2021-03-27 08:17:15 +00:00
|
|
|
):
|
2023-01-18 09:44:18 +00:00
|
|
|
await hass.config_entries.flow.async_init("bla", context={"source": "user"})
|
2023-08-07 06:25:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_init_custom_integration_with_missing_handler(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
) -> None:
|
|
|
|
"""Test initializing flow for custom integration with a missing handler."""
|
|
|
|
integration = loader.Integration(
|
|
|
|
hass,
|
|
|
|
"custom_components.hue",
|
|
|
|
None,
|
|
|
|
{"name": "Hue", "dependencies": [], "requirements": [], "domain": "hue"},
|
|
|
|
)
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule("hue"),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "hue.config_flow", None)
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
pytest.raises(data_entry_flow.UnknownHandler),
|
|
|
|
patch(
|
|
|
|
"homeassistant.loader.async_get_integration",
|
|
|
|
return_value=integration,
|
|
|
|
),
|
2023-08-07 06:25:03 +00:00
|
|
|
):
|
|
|
|
await hass.config_entries.flow.async_init("bla", context={"source": "user"})
|
2019-08-23 00:32:43 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_support_entry_unload(hass: HomeAssistant) -> None:
|
2019-08-23 00:32:43 +00:00
|
|
|
"""Test unloading entry."""
|
|
|
|
assert await config_entries.support_entry_unload(hass, "light")
|
|
|
|
assert not await config_entries.support_entry_unload(hass, "auth")
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_reload_entry_entity_registry_ignores_no_entry(
|
2023-02-09 01:15:29 +00:00
|
|
|
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
2023-02-08 07:51:43 +00:00
|
|
|
) -> None:
|
2019-08-23 00:32:43 +00:00
|
|
|
"""Test reloading entry in entity registry skips if no config entry linked."""
|
|
|
|
handler = config_entries.EntityRegistryDisabledHandler(hass)
|
|
|
|
|
|
|
|
# Test we ignore entities without config entry
|
2023-02-09 01:15:29 +00:00
|
|
|
entry = entity_registry.async_get_or_create("light", "hue", "123")
|
|
|
|
entity_registry.async_update_entity(
|
2021-12-15 21:25:40 +00:00
|
|
|
entry.entity_id, disabled_by=er.RegistryEntryDisabler.USER
|
|
|
|
)
|
2019-08-23 00:32:43 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert not handler.changed
|
|
|
|
assert handler._remove_call_later is None
|
|
|
|
|
|
|
|
|
2023-02-09 01:15:29 +00:00
|
|
|
async def test_reload_entry_entity_registry_works(
|
|
|
|
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
|
|
|
) -> None:
|
2019-08-23 00:32:43 +00:00
|
|
|
"""Test we schedule an entry to be reloaded if disabled_by is updated."""
|
|
|
|
handler = config_entries.EntityRegistryDisabledHandler(hass)
|
|
|
|
handler.async_setup()
|
|
|
|
|
|
|
|
config_entry = MockConfigEntry(
|
2021-05-20 17:19:20 +00:00
|
|
|
domain="comp", state=config_entries.ConfigEntryState.LOADED
|
2019-08-23 00:32:43 +00:00
|
|
|
)
|
2020-08-25 22:59:22 +00:00
|
|
|
config_entry.supports_unload = True
|
2019-08-23 00:32:43 +00:00
|
|
|
config_entry.add_to_hass(hass)
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
mock_unload_entry = AsyncMock(return_value=True)
|
2019-08-23 00:32:43 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=mock_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-08-23 00:32:43 +00:00
|
|
|
|
|
|
|
# Only changing disabled_by should update trigger
|
2023-02-09 01:15:29 +00:00
|
|
|
entity_entry = entity_registry.async_get_or_create(
|
2019-08-23 00:32:43 +00:00
|
|
|
"light", "hue", "123", config_entry=config_entry
|
|
|
|
)
|
2023-02-09 01:15:29 +00:00
|
|
|
entity_registry.async_update_entity(entity_entry.entity_id, name="yo")
|
2019-08-23 00:32:43 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert not handler.changed
|
|
|
|
assert handler._remove_call_later is None
|
|
|
|
|
|
|
|
# Disable entity, we should not do anything, only act when enabled.
|
2023-02-09 01:15:29 +00:00
|
|
|
entity_registry.async_update_entity(
|
2021-12-15 21:25:40 +00:00
|
|
|
entity_entry.entity_id, disabled_by=er.RegistryEntryDisabler.USER
|
|
|
|
)
|
2019-08-23 00:32:43 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert not handler.changed
|
|
|
|
assert handler._remove_call_later is None
|
|
|
|
|
|
|
|
# Enable entity, check we are reloading config entry.
|
2023-02-09 01:15:29 +00:00
|
|
|
entity_registry.async_update_entity(entity_entry.entity_id, disabled_by=None)
|
2019-08-23 00:32:43 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert handler.changed == {config_entry.entry_id}
|
|
|
|
assert handler._remove_call_later is not None
|
|
|
|
|
|
|
|
async_fire_time_changed(
|
|
|
|
hass,
|
2023-05-30 20:30:31 +00:00
|
|
|
dt_util.utcnow()
|
|
|
|
+ timedelta(seconds=config_entries.RELOAD_AFTER_UPDATE_DELAY + 1),
|
2019-08-23 00:32:43 +00:00
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2024-04-30 23:47:12 +00:00
|
|
|
assert len(mock_unload_entry.mock_calls) == 1
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_unique_id_persisted(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-12-16 11:27:43 +00:00
|
|
|
"""Test that a unique ID is stored in the config entry."""
|
2020-04-30 20:29:50 +00:00
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test user step."""
|
2019-12-16 11:27:43 +00:00
|
|
|
await self.async_set_unique_id("mock-unique-id")
|
|
|
|
return self.async_create_entry(title="mock-title", data={})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
p_hass, p_entry = mock_setup_entry.mock_calls[0][1]
|
|
|
|
|
|
|
|
assert p_hass is hass
|
|
|
|
assert p_entry.unique_id == "mock-unique-id"
|
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_unique_id_existing_entry(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-12-16 11:27:43 +00:00
|
|
|
"""Test that we remove an entry if there already is an entry with unique ID."""
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="comp",
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.LOADED,
|
2019-12-16 11:27:43 +00:00
|
|
|
unique_id="mock-unique-id",
|
|
|
|
).add_to_hass(hass)
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
|
|
|
async_remove_entry = AsyncMock(return_value=True)
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=async_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
async_remove_entry=async_remove_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test user step."""
|
2019-12-16 11:27:43 +00:00
|
|
|
existing_entry = await self.async_set_unique_id("mock-unique-id")
|
|
|
|
|
|
|
|
assert existing_entry is not None
|
|
|
|
|
|
|
|
return self.async_create_entry(title="mock-title", data={"via": "flow"})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
entries = hass.config_entries.async_entries("comp")
|
|
|
|
assert len(entries) == 1
|
|
|
|
assert entries[0].data == {"via": "flow"}
|
|
|
|
|
|
|
|
assert len(async_setup_entry.mock_calls) == 1
|
|
|
|
assert len(async_unload_entry.mock_calls) == 1
|
|
|
|
assert len(async_remove_entry.mock_calls) == 1
|
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_id_existing_entry(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2021-03-22 04:44:29 +00:00
|
|
|
"""Test that we throw when the entry id collides."""
|
|
|
|
collide_entry_id = "collide"
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
MockConfigEntry(
|
|
|
|
entry_id=collide_entry_id,
|
|
|
|
domain="comp",
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.LOADED,
|
2021-03-22 04:44:29 +00:00
|
|
|
unique_id="mock-unique-id",
|
|
|
|
).add_to_hass(hass)
|
|
|
|
|
2021-03-22 05:29:48 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule("comp"),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2021-03-22 05:29:48 +00:00
|
|
|
|
2021-03-22 04:44:29 +00:00
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
|
|
|
"""Test user step."""
|
|
|
|
return self.async_create_entry(title="mock-title", data={"via": "flow"})
|
|
|
|
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
pytest.raises(HomeAssistantError),
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
patch(
|
2024-06-03 08:34:09 +00:00
|
|
|
"homeassistant.config_entries.ulid_util.ulid_now",
|
2024-03-25 23:02:16 +00:00
|
|
|
return_value=collide_entry_id,
|
|
|
|
),
|
2021-03-22 04:44:29 +00:00
|
|
|
):
|
|
|
|
await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_unique_id_update_existing_entry_without_reload(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2020-01-23 19:21:19 +00:00
|
|
|
"""Test that we update an entry if there already is an entry with unique ID."""
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
data={"additional": "data", "host": "0.0.0.0"},
|
|
|
|
unique_id="mock-unique-id",
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.LOADED,
|
2020-01-23 19:21:19 +00:00
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_integration(
|
2020-08-27 11:56:20 +00:00
|
|
|
hass,
|
|
|
|
MockModule("comp"),
|
2020-01-23 19:21:19 +00:00
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-01-23 19:21:19 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2020-01-23 19:21:19 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test user step."""
|
2020-01-23 19:21:19 +00:00
|
|
|
await self.async_set_unique_id("mock-unique-id")
|
2022-05-31 03:24:34 +00:00
|
|
|
self._abort_if_unique_id_configured(
|
2020-08-08 18:23:56 +00:00
|
|
|
updates={"host": "1.1.1.1"}, reload_on_update=False
|
|
|
|
)
|
2020-01-23 19:21:19 +00:00
|
|
|
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
patch(
|
|
|
|
"homeassistant.config_entries.ConfigEntries.async_reload"
|
|
|
|
) as async_reload,
|
|
|
|
):
|
2020-08-08 18:23:56 +00:00
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2022-07-07 19:28:18 +00:00
|
|
|
assert result["type"] == FlowResultType.ABORT
|
2020-08-08 18:23:56 +00:00
|
|
|
assert result["reason"] == "already_configured"
|
|
|
|
assert entry.data["host"] == "1.1.1.1"
|
|
|
|
assert entry.data["additional"] == "data"
|
|
|
|
assert len(async_reload.mock_calls) == 0
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_unique_id_update_existing_entry_with_reload(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2020-08-08 18:23:56 +00:00
|
|
|
"""Test that we update an entry if there already is an entry with unique ID and we reload on changes."""
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
data={"additional": "data", "host": "0.0.0.0"},
|
|
|
|
unique_id="mock-unique-id",
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.LOADED,
|
2020-08-08 18:23:56 +00:00
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_integration(
|
2020-08-27 11:56:20 +00:00
|
|
|
hass,
|
|
|
|
MockModule("comp"),
|
2020-08-08 18:23:56 +00:00
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-08-24 08:54:26 +00:00
|
|
|
updates = {"host": "1.1.1.1"}
|
2020-08-08 18:23:56 +00:00
|
|
|
|
|
|
|
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")
|
|
|
|
await self._abort_if_unique_id_configured(
|
2020-08-24 08:54:26 +00:00
|
|
|
updates=updates, reload_on_update=True
|
2020-08-08 18:23:56 +00:00
|
|
|
)
|
|
|
|
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
patch(
|
|
|
|
"homeassistant.config_entries.ConfigEntries.async_reload"
|
|
|
|
) as async_reload,
|
|
|
|
):
|
2020-01-23 19:21:19 +00:00
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
2020-08-08 18:23:56 +00:00
|
|
|
await hass.async_block_till_done()
|
2020-01-23 19:21:19 +00:00
|
|
|
|
2022-07-07 19:28:18 +00:00
|
|
|
assert result["type"] == FlowResultType.ABORT
|
2020-01-23 19:21:19 +00:00
|
|
|
assert result["reason"] == "already_configured"
|
|
|
|
assert entry.data["host"] == "1.1.1.1"
|
|
|
|
assert entry.data["additional"] == "data"
|
2020-08-08 18:23:56 +00:00
|
|
|
assert len(async_reload.mock_calls) == 1
|
2020-01-23 19:21:19 +00:00
|
|
|
|
2020-08-24 08:54:26 +00:00
|
|
|
# Test we don't reload if entry not started
|
|
|
|
updates["host"] = "2.2.2.2"
|
2024-02-16 16:15:05 +00:00
|
|
|
entry._async_set_state(hass, config_entries.ConfigEntryState.NOT_LOADED, None)
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
patch(
|
|
|
|
"homeassistant.config_entries.ConfigEntries.async_reload"
|
|
|
|
) as async_reload,
|
|
|
|
):
|
2020-08-24 08:54:26 +00:00
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2022-07-07 19:28:18 +00:00
|
|
|
assert result["type"] == FlowResultType.ABORT
|
2020-08-24 08:54:26 +00:00
|
|
|
assert result["reason"] == "already_configured"
|
|
|
|
assert entry.data["host"] == "2.2.2.2"
|
|
|
|
assert entry.data["additional"] == "data"
|
|
|
|
assert len(async_reload.mock_calls) == 0
|
|
|
|
|
2020-01-23 19:21:19 +00:00
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_unique_id_from_discovery_in_setup_retry(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2022-05-31 03:24:34 +00:00
|
|
|
"""Test that we reload when in a setup retry state from discovery."""
|
|
|
|
hass.config.components.add("comp")
|
2024-02-14 22:32:02 +00:00
|
|
|
unique_id = "34ea34b43b5a"
|
2022-05-31 03:24:34 +00:00
|
|
|
host = "0.0.0.0"
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
data={"additional": "data", "host": host},
|
|
|
|
unique_id=unique_id,
|
|
|
|
state=config_entries.ConfigEntryState.SETUP_RETRY,
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule("comp"),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2022-05-31 03:24:34 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_dhcp(
|
|
|
|
self, discovery_info: dhcp.DhcpServiceInfo
|
|
|
|
) -> FlowResult:
|
|
|
|
"""Test dhcp step."""
|
|
|
|
await self.async_set_unique_id(unique_id)
|
|
|
|
self._abort_if_unique_id_configured()
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input: dict | None = None) -> FlowResult:
|
|
|
|
"""Test user step."""
|
|
|
|
await self.async_set_unique_id(unique_id)
|
|
|
|
self._abort_if_unique_id_configured()
|
|
|
|
|
|
|
|
# Verify we do not reload from a user source
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
patch(
|
|
|
|
"homeassistant.config_entries.ConfigEntries.async_reload"
|
|
|
|
) as async_reload,
|
|
|
|
):
|
2022-05-31 03:24:34 +00:00
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2022-07-07 19:28:18 +00:00
|
|
|
assert result["type"] == FlowResultType.ABORT
|
2022-05-31 03:24:34 +00:00
|
|
|
assert result["reason"] == "already_configured"
|
|
|
|
assert len(async_reload.mock_calls) == 0
|
|
|
|
|
|
|
|
# Verify do reload from a discovery source
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
patch(
|
|
|
|
"homeassistant.config_entries.ConfigEntries.async_reload"
|
|
|
|
) as async_reload,
|
|
|
|
):
|
2022-05-31 03:24:34 +00:00
|
|
|
discovery_result = await manager.flow.async_init(
|
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_DHCP},
|
|
|
|
data=dhcp.DhcpServiceInfo(
|
|
|
|
hostname="any",
|
|
|
|
ip=host,
|
|
|
|
macaddress=unique_id,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2022-07-07 19:28:18 +00:00
|
|
|
assert discovery_result["type"] == FlowResultType.ABORT
|
2022-05-31 03:24:34 +00:00
|
|
|
assert discovery_result["reason"] == "already_configured"
|
|
|
|
assert len(async_reload.mock_calls) == 1
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_unique_id_not_update_existing_entry(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2020-01-23 19:21:19 +00:00
|
|
|
"""Test that we do not update an entry if existing entry has the data."""
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
data={"additional": "data", "host": "0.0.0.0"},
|
|
|
|
unique_id="mock-unique-id",
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_integration(
|
2020-08-27 11:56:20 +00:00
|
|
|
hass,
|
|
|
|
MockModule("comp"),
|
2020-01-23 19:21:19 +00:00
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-01-23 19:21:19 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2020-01-23 19:21:19 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test user step."""
|
2020-01-23 19:21:19 +00:00
|
|
|
await self.async_set_unique_id("mock-unique-id")
|
2020-08-08 18:23:56 +00:00
|
|
|
await self._abort_if_unique_id_configured(
|
|
|
|
updates={"host": "0.0.0.0"}, reload_on_update=True
|
|
|
|
)
|
2020-01-23 19:21:19 +00:00
|
|
|
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
patch(
|
|
|
|
"homeassistant.config_entries.ConfigEntries.async_reload"
|
|
|
|
) as async_reload,
|
|
|
|
):
|
2020-01-23 19:21:19 +00:00
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
2020-08-08 18:23:56 +00:00
|
|
|
await hass.async_block_till_done()
|
2020-01-23 19:21:19 +00:00
|
|
|
|
2022-07-07 19:28:18 +00:00
|
|
|
assert result["type"] == FlowResultType.ABORT
|
2020-01-23 19:21:19 +00:00
|
|
|
assert result["reason"] == "already_configured"
|
|
|
|
assert entry.data["host"] == "0.0.0.0"
|
|
|
|
assert entry.data["additional"] == "data"
|
2020-08-08 18:23:56 +00:00
|
|
|
assert len(async_reload.mock_calls) == 0
|
2020-01-23 19:21:19 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_unique_id_in_progress(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-12-16 11:27:43 +00:00
|
|
|
"""Test that we abort if there is already a flow in progress with same unique id."""
|
|
|
|
mock_integration(hass, MockModule("comp"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test user step."""
|
2019-12-16 11:27:43 +00:00
|
|
|
await self.async_set_unique_id("mock-unique-id")
|
|
|
|
return self.async_show_form(step_id="discovery")
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
# Create one to be in progress
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
2019-12-16 11:27:43 +00:00
|
|
|
|
|
|
|
# Will be canceled
|
|
|
|
result2 = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result2["type"] == data_entry_flow.FlowResultType.ABORT
|
2019-12-16 11:27:43 +00:00
|
|
|
assert result2["reason"] == "already_in_progress"
|
2019-12-16 18:45:09 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_finish_flow_aborts_progress(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-12-16 18:45:09 +00:00
|
|
|
"""Test that when finishing a flow, we abort other flows in progress with unique ID."""
|
|
|
|
mock_integration(
|
2020-08-27 11:56:20 +00:00
|
|
|
hass,
|
|
|
|
MockModule("comp", async_setup_entry=AsyncMock(return_value=True)),
|
2019-12-16 18:45:09 +00:00
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-12-16 18:45:09 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2019-12-16 18:45:09 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test user step."""
|
2019-12-16 18:45:09 +00:00
|
|
|
await self.async_set_unique_id("mock-unique-id", raise_on_progress=False)
|
|
|
|
|
|
|
|
if user_input is None:
|
|
|
|
return self.async_show_form(step_id="discovery")
|
|
|
|
|
|
|
|
return self.async_create_entry(title="yo", data={})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
# Create one to be in progress
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
2019-12-16 18:45:09 +00:00
|
|
|
|
|
|
|
# Will finish and cancel other one.
|
|
|
|
result2 = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}, data={}
|
|
|
|
)
|
|
|
|
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2019-12-16 18:45:09 +00:00
|
|
|
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 0
|
2019-12-18 06:41:01 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_unique_id_ignore(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-12-18 06:41:01 +00:00
|
|
|
"""Test that we can ignore flows that are in progress and have a unique ID."""
|
2020-04-30 20:29:50 +00:00
|
|
|
async_setup_entry = AsyncMock(return_value=False)
|
2019-12-18 06:41:01 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-12-18 06:41:01 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2019-12-18 06:41:01 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test user flow."""
|
2019-12-18 06:41:01 +00:00
|
|
|
await self.async_set_unique_id("mock-unique-id")
|
|
|
|
return self.async_show_form(step_id="discovery")
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
# Create one to be in progress
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
2019-12-18 06:41:01 +00:00
|
|
|
|
|
|
|
result2 = await manager.flow.async_init(
|
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_IGNORE},
|
2021-01-12 08:26:20 +00:00
|
|
|
data={"unique_id": "mock-unique-id", "title": "Ignored Title"},
|
2019-12-18 06:41:01 +00:00
|
|
|
)
|
|
|
|
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2019-12-18 06:41:01 +00:00
|
|
|
|
|
|
|
# assert len(hass.config_entries.flow.async_progress()) == 0
|
|
|
|
|
|
|
|
# We should never set up an ignored entry.
|
|
|
|
assert len(async_setup_entry.mock_calls) == 0
|
|
|
|
|
|
|
|
entry = hass.config_entries.async_entries("comp")[0]
|
|
|
|
|
|
|
|
assert entry.source == "ignore"
|
|
|
|
assert entry.unique_id == "mock-unique-id"
|
2021-01-12 08:26:20 +00:00
|
|
|
assert entry.title == "Ignored Title"
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_manual_add_overrides_ignored_entry(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2020-12-07 08:25:04 +00:00
|
|
|
"""Test that we can ignore manually add entry, overriding ignored entry."""
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
data={"additional": "data", "host": "0.0.0.0"},
|
|
|
|
unique_id="mock-unique-id",
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.LOADED,
|
2020-12-07 08:25:04 +00:00
|
|
|
source=config_entries.SOURCE_IGNORE,
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule("comp"),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-12-07 08:25:04 +00:00
|
|
|
|
|
|
|
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(
|
|
|
|
updates={"host": "1.1.1.1"}, reload_on_update=False
|
|
|
|
)
|
|
|
|
return self.async_show_form(step_id="step2")
|
|
|
|
|
2023-10-19 11:34:10 +00:00
|
|
|
async def async_step_step2(self, user_input=None):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
patch(
|
|
|
|
"homeassistant.config_entries.ConfigEntries.async_reload"
|
|
|
|
) as async_reload,
|
|
|
|
):
|
2020-12-07 08:25:04 +00:00
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
2020-12-07 08:25:04 +00:00
|
|
|
assert entry.data["host"] == "1.1.1.1"
|
|
|
|
assert entry.data["additional"] == "data"
|
|
|
|
assert len(async_reload.mock_calls) == 0
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_manual_add_overrides_ignored_entry_singleton(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-02-04 10:08:10 +00:00
|
|
|
"""Test that we can ignore manually add entry, overriding ignored entry."""
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.LOADED,
|
2021-02-04 10:08:10 +00:00
|
|
|
source=config_entries.SOURCE_IGNORE,
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2021-02-04 10:08:10 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
|
|
|
"""Test user step."""
|
|
|
|
if self._async_current_entries():
|
|
|
|
return self.async_abort(reason="single_instance_allowed")
|
|
|
|
return self.async_create_entry(title="title", data={"token": "supersecret"})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}):
|
|
|
|
await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
p_hass, p_entry = mock_setup_entry.mock_calls[0][1]
|
|
|
|
|
|
|
|
assert p_hass is hass
|
|
|
|
assert p_entry.data == {"token": "supersecret"}
|
|
|
|
|
|
|
|
|
2024-05-11 19:45:03 +00:00
|
|
|
async def test_async_current_entries_does_not_skip_ignore_non_user(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-03-22 04:57:49 +00:00
|
|
|
"""Test that _async_current_entries does not skip ignore by default for non user step."""
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.LOADED,
|
2021-03-22 04:57:49 +00:00
|
|
|
source=config_entries.SOURCE_IGNORE,
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2021-03-22 04:57:49 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_import(self, user_input=None):
|
|
|
|
"""Test not the user step."""
|
|
|
|
if self._async_current_entries():
|
|
|
|
return self.async_abort(reason="single_instance_allowed")
|
|
|
|
return self.async_create_entry(title="title", data={"token": "supersecret"})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}):
|
|
|
|
await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_IMPORT}
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 0
|
|
|
|
|
|
|
|
|
2024-05-11 19:45:03 +00:00
|
|
|
async def test_async_current_entries_explicit_skip_ignore(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-03-22 04:57:49 +00:00
|
|
|
"""Test that _async_current_entries can explicitly include ignore."""
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.LOADED,
|
2021-03-22 04:57:49 +00:00
|
|
|
source=config_entries.SOURCE_IGNORE,
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2021-03-22 04:57:49 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_import(self, user_input=None):
|
|
|
|
"""Test not the user step."""
|
|
|
|
if self._async_current_entries(include_ignore=False):
|
|
|
|
return self.async_abort(reason="single_instance_allowed")
|
|
|
|
return self.async_create_entry(title="title", data={"token": "supersecret"})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}):
|
|
|
|
await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_IMPORT}
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
p_hass, p_entry = mock_setup_entry.mock_calls[0][1]
|
|
|
|
|
|
|
|
assert p_hass is hass
|
|
|
|
assert p_entry.data == {"token": "supersecret"}
|
|
|
|
|
|
|
|
|
2024-05-11 19:45:03 +00:00
|
|
|
async def test_async_current_entries_explicit_include_ignore(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-03-22 04:57:49 +00:00
|
|
|
"""Test that _async_current_entries can explicitly include ignore."""
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.LOADED,
|
2021-03-22 04:57:49 +00:00
|
|
|
source=config_entries.SOURCE_IGNORE,
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2021-03-22 04:57:49 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_import(self, user_input=None):
|
|
|
|
"""Test not the user step."""
|
|
|
|
if self._async_current_entries(include_ignore=True):
|
|
|
|
return self.async_abort(reason="single_instance_allowed")
|
|
|
|
return self.async_create_entry(title="title", data={"token": "supersecret"})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}):
|
|
|
|
await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_IMPORT}
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 0
|
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_unignore_step_form(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-12-21 10:22:07 +00:00
|
|
|
"""Test that we can ignore flows that are in progress and have a unique ID, then rediscover them."""
|
2020-04-30 20:29:50 +00:00
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
2019-12-21 10:22:07 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_unignore(self, user_input):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test unignore step."""
|
2019-12-21 10:22:07 +00:00
|
|
|
unique_id = user_input["unique_id"]
|
|
|
|
await self.async_set_unique_id(unique_id)
|
|
|
|
return self.async_show_form(step_id="discovery")
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_IGNORE},
|
2021-01-12 08:26:20 +00:00
|
|
|
data={"unique_id": "mock-unique-id", "title": "Ignored Title"},
|
2019-12-21 10:22:07 +00:00
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
entry = hass.config_entries.async_entries("comp")[0]
|
|
|
|
assert entry.source == "ignore"
|
|
|
|
assert entry.unique_id == "mock-unique-id"
|
|
|
|
assert entry.domain == "comp"
|
2021-01-12 08:26:20 +00:00
|
|
|
assert entry.title == "Ignored Title"
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
await manager.async_remove(entry.entry_id)
|
|
|
|
|
|
|
|
# But after a 'tick' the unignore step has run and we can see an active flow again.
|
|
|
|
await hass.async_block_till_done()
|
2021-10-22 17:19:49 +00:00
|
|
|
assert len(hass.config_entries.flow.async_progress_by_handler("comp")) == 1
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
# and still not config entries
|
|
|
|
assert len(hass.config_entries.async_entries("comp")) == 0
|
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_unignore_create_entry(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-12-21 10:22:07 +00:00
|
|
|
"""Test that we can ignore flows that are in progress and have a unique ID, then rediscover them."""
|
2020-04-30 20:29:50 +00:00
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
2019-12-21 10:22:07 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_unignore(self, user_input):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test unignore step."""
|
2019-12-21 10:22:07 +00:00
|
|
|
unique_id = user_input["unique_id"]
|
|
|
|
await self.async_set_unique_id(unique_id)
|
|
|
|
return self.async_create_entry(title="yo", data={})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_IGNORE},
|
2021-01-12 08:26:20 +00:00
|
|
|
data={"unique_id": "mock-unique-id", "title": "Ignored Title"},
|
2019-12-21 10:22:07 +00:00
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
entry = hass.config_entries.async_entries("comp")[0]
|
|
|
|
assert entry.source == "ignore"
|
|
|
|
assert entry.unique_id == "mock-unique-id"
|
|
|
|
assert entry.domain == "comp"
|
2021-01-12 08:26:20 +00:00
|
|
|
assert entry.title == "Ignored Title"
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
await manager.async_remove(entry.entry_id)
|
|
|
|
|
|
|
|
# But after a 'tick' the unignore step has run and we can see a config entry.
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
entry = hass.config_entries.async_entries("comp")[0]
|
2021-04-25 09:27:40 +00:00
|
|
|
assert entry.source == config_entries.SOURCE_UNIGNORE
|
2019-12-21 10:22:07 +00:00
|
|
|
assert entry.unique_id == "mock-unique-id"
|
|
|
|
assert entry.title == "yo"
|
|
|
|
|
|
|
|
# And still no active flow
|
2021-10-22 17:19:49 +00:00
|
|
|
assert len(hass.config_entries.flow.async_progress_by_handler("comp")) == 0
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_unignore_default_impl(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2019-12-21 10:22:07 +00:00
|
|
|
"""Test that resdicovery is a no-op by default."""
|
2020-04-30 20:29:50 +00:00
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
2019-12-21 10:22:07 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_IGNORE},
|
2021-01-12 08:26:20 +00:00
|
|
|
data={"unique_id": "mock-unique-id", "title": "Ignored Title"},
|
2019-12-21 10:22:07 +00:00
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
entry = hass.config_entries.async_entries("comp")[0]
|
|
|
|
assert entry.source == "ignore"
|
|
|
|
assert entry.unique_id == "mock-unique-id"
|
|
|
|
assert entry.domain == "comp"
|
2021-01-12 08:26:20 +00:00
|
|
|
assert entry.title == "Ignored Title"
|
2019-12-21 10:22:07 +00:00
|
|
|
|
|
|
|
await manager.async_remove(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(hass.config_entries.async_entries("comp")) == 0
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 0
|
2020-01-03 16:28:05 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_partial_flows_hidden(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2020-01-03 16:28:05 +00:00
|
|
|
"""Test that flows that don't have a cur_step and haven't finished initing are hidden."""
|
2020-04-30 20:29:50 +00:00
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
2020-01-03 16:28:05 +00:00
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=async_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-01-03 16:28:05 +00:00
|
|
|
|
|
|
|
# A flag to test our assertion that `async_step_discovery` was called and is in its blocked state
|
|
|
|
# This simulates if the step was e.g. doing network i/o
|
|
|
|
discovery_started = asyncio.Event()
|
|
|
|
|
|
|
|
# A flag to allow `async_step_discovery` to resume after we have verified the uninited flow is not
|
|
|
|
# visible and has not triggered a discovery alert. This lets us control when the mocked network
|
|
|
|
# i/o is complete.
|
|
|
|
pause_discovery = asyncio.Event()
|
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test flow."""
|
2020-01-03 16:28:05 +00:00
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
2020-06-15 11:38:38 +00:00
|
|
|
async def async_step_discovery(self, discovery_info):
|
2020-04-07 16:33:23 +00:00
|
|
|
"""Test discovery step."""
|
2020-01-03 16:28:05 +00:00
|
|
|
discovery_started.set()
|
|
|
|
await pause_discovery.wait()
|
|
|
|
return self.async_show_form(step_id="someform")
|
|
|
|
|
2023-10-19 11:34:10 +00:00
|
|
|
async def async_step_someform(self, user_input=None):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-01-03 16:28:05 +00:00
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
# Start a config entry flow and wait for it to be blocked
|
|
|
|
init_task = asyncio.ensure_future(
|
|
|
|
manager.flow.async_init(
|
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_DISCOVERY},
|
|
|
|
data={"unique_id": "mock-unique-id"},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
await discovery_started.wait()
|
|
|
|
|
|
|
|
# While it's blocked it shouldn't be visible or trigger discovery notifications
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 0
|
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
2023-05-26 03:09:13 +00:00
|
|
|
notifications = async_get_persistent_notifications(hass)
|
|
|
|
assert "config_entry_discovery" not in notifications
|
2020-01-03 16:28:05 +00:00
|
|
|
|
|
|
|
# Let the flow init complete
|
|
|
|
pause_discovery.set()
|
|
|
|
|
|
|
|
# When it's complete it should now be visible in async_progress and have triggered
|
|
|
|
# discovery notifications
|
|
|
|
result = await init_task
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
2020-01-03 16:28:05 +00:00
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
2023-05-26 03:09:13 +00:00
|
|
|
notifications = async_get_persistent_notifications(hass)
|
|
|
|
assert "config_entry_discovery" in notifications
|
2020-04-15 01:46:41 +00:00
|
|
|
|
|
|
|
|
2023-06-16 02:15:07 +00:00
|
|
|
async def test_async_setup_init_entry(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2020-04-15 01:46:41 +00:00
|
|
|
"""Test a config entry being initialized during integration setup."""
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def mock_async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
2020-04-15 01:46:41 +00:00
|
|
|
"""Mock setup."""
|
|
|
|
hass.async_create_task(
|
|
|
|
hass.config_entries.flow.async_init(
|
2020-08-27 11:56:20 +00:00
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_IMPORT},
|
|
|
|
data={},
|
2020-04-15 01:46:41 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
2020-04-30 20:29:50 +00:00
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
2020-04-15 01:46:41 +00:00
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp", async_setup=mock_async_setup, async_setup_entry=async_setup_entry
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-04-15 01:46:41 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_import(self, user_input):
|
|
|
|
"""Test import step creating entry."""
|
|
|
|
return self.async_create_entry(title="title", data={})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
assert await async_setup_component(hass, "comp", {})
|
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(async_setup_entry.mock_calls) == 1
|
|
|
|
|
|
|
|
entries = hass.config_entries.async_entries("comp")
|
|
|
|
assert len(entries) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entries[0].state is config_entries.ConfigEntryState.LOADED
|
2020-04-15 01:46:41 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_async_setup_init_entry_completes_before_loaded_event_fires(
|
2023-06-16 02:15:07 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-08 07:51:43 +00:00
|
|
|
) -> None:
|
2022-04-06 23:06:22 +00:00
|
|
|
"""Test a config entry being initialized during integration setup before the loaded event fires."""
|
2024-02-18 20:17:41 +00:00
|
|
|
load_events = async_capture_events(hass, EVENT_COMPONENT_LOADED)
|
2022-04-06 23:06:22 +00:00
|
|
|
|
|
|
|
async def mock_async_setup(hass, config):
|
|
|
|
"""Mock setup."""
|
|
|
|
hass.async_create_task(
|
|
|
|
hass.config_entries.flow.async_init(
|
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_IMPORT},
|
|
|
|
data={},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
|
|
async_setup_entry = AsyncMock(return_value=True)
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp", async_setup=mock_async_setup, async_setup_entry=async_setup_entry
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2022-04-06 23:06:22 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_three(self, user_input=None):
|
|
|
|
"""Test import step creating entry."""
|
|
|
|
return self.async_create_entry(title="title", data={})
|
|
|
|
|
|
|
|
async def async_step_two(self, user_input=None):
|
|
|
|
"""Test import step creating entry."""
|
|
|
|
return await self.async_step_three()
|
|
|
|
|
|
|
|
async def async_step_one(self, user_input=None):
|
|
|
|
"""Test import step creating entry."""
|
|
|
|
return await self.async_step_two()
|
|
|
|
|
|
|
|
async def async_step_import(self, user_input=None):
|
|
|
|
"""Test import step creating entry."""
|
|
|
|
return await self.async_step_one()
|
|
|
|
|
|
|
|
# This test must not use hass.async_block_till_done()
|
|
|
|
# as its explicitly testing what happens without it
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
assert await async_setup_component(hass, "comp", {})
|
|
|
|
assert len(async_setup_entry.mock_calls) == 1
|
|
|
|
assert load_events[0].event_type == EVENT_COMPONENT_LOADED
|
|
|
|
assert load_events[0].data == {"component": "comp"}
|
|
|
|
entries = hass.config_entries.async_entries("comp")
|
|
|
|
assert len(entries) == 1
|
|
|
|
assert entries[0].state is config_entries.ConfigEntryState.LOADED
|
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_async_setup_update_entry(hass: HomeAssistant) -> None:
|
2020-04-15 01:46:41 +00:00
|
|
|
"""Test a config entry being updated during integration setup."""
|
|
|
|
entry = MockConfigEntry(domain="comp", data={"value": "initial"})
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async def mock_async_setup(hass, config):
|
|
|
|
"""Mock setup."""
|
|
|
|
hass.async_create_task(
|
|
|
|
hass.config_entries.flow.async_init(
|
2020-08-27 11:56:20 +00:00
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_IMPORT},
|
|
|
|
data={},
|
2020-04-15 01:46:41 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def mock_async_setup_entry(hass, entry):
|
|
|
|
"""Mock setting up an entry."""
|
|
|
|
assert entry.data["value"] == "updated"
|
|
|
|
return True
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=mock_async_setup,
|
|
|
|
async_setup_entry=mock_async_setup_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-04-15 01:46:41 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_import(self, user_input):
|
|
|
|
"""Test import step updating existing entry."""
|
2020-08-08 18:23:56 +00:00
|
|
|
assert (
|
|
|
|
self.hass.config_entries.async_update_entry(
|
|
|
|
entry, data={"value": "updated"}
|
|
|
|
)
|
|
|
|
is True
|
2020-04-15 01:46:41 +00:00
|
|
|
)
|
|
|
|
return self.async_abort(reason="yo")
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
assert await async_setup_component(hass, "comp", {})
|
|
|
|
|
|
|
|
entries = hass.config_entries.async_entries("comp")
|
|
|
|
assert len(entries) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entries[0].state is config_entries.ConfigEntryState.LOADED
|
2020-04-15 01:46:41 +00:00
|
|
|
assert entries[0].data == {"value": "updated"}
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"discovery_source",
|
2024-03-19 08:01:07 +00:00
|
|
|
[
|
2022-07-08 23:55:31 +00:00
|
|
|
(config_entries.SOURCE_BLUETOOTH, BaseServiceInfo()),
|
2021-11-25 01:30:02 +00:00
|
|
|
(config_entries.SOURCE_DISCOVERY, {}),
|
2021-11-29 16:10:07 +00:00
|
|
|
(config_entries.SOURCE_SSDP, BaseServiceInfo()),
|
2021-11-25 01:30:02 +00:00
|
|
|
(config_entries.SOURCE_USB, BaseServiceInfo()),
|
|
|
|
(config_entries.SOURCE_HOMEKIT, BaseServiceInfo()),
|
|
|
|
(config_entries.SOURCE_DHCP, BaseServiceInfo()),
|
|
|
|
(config_entries.SOURCE_ZEROCONF, BaseServiceInfo()),
|
2022-10-11 14:56:45 +00:00
|
|
|
(
|
|
|
|
config_entries.SOURCE_HASSIO,
|
2023-04-25 07:48:47 +00:00
|
|
|
HassioServiceInfo(config={}, name="Test", slug="test", uuid="1234"),
|
2022-10-11 14:56:45 +00:00
|
|
|
),
|
2024-03-19 08:01:07 +00:00
|
|
|
],
|
2020-06-15 11:38:38 +00:00
|
|
|
)
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_flow_with_default_discovery(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
discovery_source: tuple[str, dict | BaseServiceInfo],
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2020-06-15 11:38:38 +00:00
|
|
|
"""Test that finishing a default discovery flow removes the unique ID in the entry."""
|
|
|
|
mock_integration(
|
2020-08-27 11:56:20 +00:00
|
|
|
hass,
|
|
|
|
MockModule("comp", async_setup_entry=AsyncMock(return_value=True)),
|
2020-06-15 11:38:38 +00:00
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
|
|
|
"""Test user step."""
|
|
|
|
if user_input is None:
|
|
|
|
return self.async_show_form(step_id="user")
|
|
|
|
|
|
|
|
return self.async_create_entry(title="yo", data={})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
# Create one to be in progress
|
|
|
|
result = await manager.flow.async_init(
|
2021-11-25 01:30:02 +00:00
|
|
|
"comp", context={"source": discovery_source[0]}, data=discovery_source[1]
|
2020-06-15 11:38:38 +00:00
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
assert (
|
|
|
|
flows[0]["context"]["unique_id"]
|
|
|
|
== config_entries.DEFAULT_DISCOVERY_UNIQUE_ID
|
|
|
|
)
|
|
|
|
|
|
|
|
# Finish flow
|
|
|
|
result2 = await manager.flow.async_configure(
|
|
|
|
result["flow_id"], user_input={"fake": "data"}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 0
|
|
|
|
|
|
|
|
entry = hass.config_entries.async_entries("comp")[0]
|
|
|
|
assert entry.title == "yo"
|
2021-11-25 01:30:02 +00:00
|
|
|
assert entry.source == discovery_source[0]
|
2020-06-15 11:38:38 +00:00
|
|
|
assert entry.unique_id is None
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_flow_with_default_discovery_with_unique_id(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2020-06-15 11:38:38 +00:00
|
|
|
"""Test discovery flow using the default discovery is ignored when unique ID is set."""
|
|
|
|
mock_integration(hass, MockModule("comp"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_discovery(self, discovery_info):
|
|
|
|
"""Test discovery step."""
|
|
|
|
await self.async_set_unique_id("mock-unique-id")
|
|
|
|
# This call should make no difference, as a unique ID is set
|
|
|
|
await self._async_handle_discovery_without_unique_id()
|
|
|
|
return self.async_show_form(step_id="mock")
|
|
|
|
|
2023-10-19 11:34:10 +00:00
|
|
|
async def async_step_mock(self, user_input=None):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-06-15 11:38:38 +00:00
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_DISCOVERY}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
assert flows[0]["context"]["unique_id"] == "mock-unique-id"
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_default_discovery_abort_existing_entries(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2020-06-15 11:38:38 +00:00
|
|
|
"""Test that a flow without discovery implementation aborts when a config entry exists."""
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
entry = MockConfigEntry(domain="comp", data={}, unique_id="mock-unique-id")
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("comp"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_DISCOVERY}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
2020-06-15 11:38:38 +00:00
|
|
|
assert result["reason"] == "already_configured"
|
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_default_discovery_in_progress(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2020-06-15 11:38:38 +00:00
|
|
|
"""Test that a flow using default discovery can only be triggered once."""
|
|
|
|
mock_integration(hass, MockModule("comp"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_discovery(self, discovery_info):
|
|
|
|
"""Test discovery step."""
|
|
|
|
await self.async_set_unique_id(discovery_info.get("unique_id"))
|
|
|
|
await self._async_handle_discovery_without_unique_id()
|
|
|
|
return self.async_show_form(step_id="mock")
|
|
|
|
|
2023-10-19 11:34:10 +00:00
|
|
|
async def async_step_mock(self, user_input=None):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-06-15 11:38:38 +00:00
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_DISCOVERY},
|
|
|
|
data={"unique_id": "mock-unique-id"},
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
# Second discovery without a unique ID
|
|
|
|
result2 = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_DISCOVERY}, data={}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result2["type"] == data_entry_flow.FlowResultType.ABORT
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
assert flows[0]["context"]["unique_id"] == "mock-unique-id"
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_default_discovery_abort_on_new_unique_flow(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2020-06-15 11:38:38 +00:00
|
|
|
"""Test that a flow using default discovery is aborted when a second flow with unique ID is created."""
|
|
|
|
mock_integration(hass, MockModule("comp"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_discovery(self, discovery_info):
|
|
|
|
"""Test discovery step."""
|
|
|
|
await self.async_set_unique_id(discovery_info.get("unique_id"))
|
|
|
|
await self._async_handle_discovery_without_unique_id()
|
|
|
|
return self.async_show_form(step_id="mock")
|
|
|
|
|
2023-10-19 11:34:10 +00:00
|
|
|
async def async_step_mock(self, user_input=None):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-06-15 11:38:38 +00:00
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
# First discovery with default, no unique ID
|
|
|
|
result2 = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_DISCOVERY}, data={}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result2["type"] == data_entry_flow.FlowResultType.FORM
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
# Second discovery brings in a unique ID
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp",
|
|
|
|
context={"source": config_entries.SOURCE_DISCOVERY},
|
|
|
|
data={"unique_id": "mock-unique-id"},
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
2020-06-15 11:38:38 +00:00
|
|
|
|
|
|
|
# Ensure the first one is cancelled and we end up with just the last one
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
assert flows[0]["context"]["unique_id"] == "mock-unique-id"
|
2020-08-08 18:23:56 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_default_discovery_abort_on_user_flow_complete(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-05-26 14:29:52 +00:00
|
|
|
"""Test that a flow using default discovery is aborted when a second flow completes."""
|
|
|
|
mock_integration(hass, MockModule("comp"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2021-05-26 14:29:52 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
|
|
|
"""Test user step."""
|
|
|
|
if user_input is None:
|
|
|
|
return self.async_show_form(step_id="user")
|
|
|
|
return self.async_create_entry(title="title", data={"token": "supersecret"})
|
|
|
|
|
|
|
|
async def async_step_discovery(self, discovery_info=None):
|
|
|
|
"""Test discovery step."""
|
|
|
|
await self._async_handle_discovery_without_unique_id()
|
|
|
|
return self.async_show_form(step_id="mock")
|
|
|
|
|
2023-10-19 11:34:10 +00:00
|
|
|
async def async_step_mock(self, user_input=None):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2021-05-26 14:29:52 +00:00
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
# First discovery with default, no unique ID
|
|
|
|
flow1 = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_DISCOVERY}, data={}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert flow1["type"] == data_entry_flow.FlowResultType.FORM
|
2021-05-26 14:29:52 +00:00
|
|
|
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
|
|
|
|
# User sets up a manual flow
|
|
|
|
flow2 = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert flow2["type"] == data_entry_flow.FlowResultType.FORM
|
2021-05-26 14:29:52 +00:00
|
|
|
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 2
|
|
|
|
|
|
|
|
# Complete the manual flow
|
|
|
|
result = await hass.config_entries.flow.async_configure(flow2["flow_id"], {})
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2021-05-26 14:29:52 +00:00
|
|
|
|
|
|
|
# Ensure the first flow is gone now
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 0
|
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_flow_same_device_multiple_sources(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2021-08-24 04:01:21 +00:00
|
|
|
"""Test discovery of the same devices from multiple discovery sources."""
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule("comp", async_setup_entry=AsyncMock(return_value=True)),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2021-08-24 04:01:21 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_zeroconf(self, discovery_info=None):
|
|
|
|
"""Test zeroconf step."""
|
|
|
|
return await self._async_discovery_handler(discovery_info)
|
|
|
|
|
|
|
|
async def async_step_homekit(self, discovery_info=None):
|
|
|
|
"""Test homekit step."""
|
|
|
|
return await self._async_discovery_handler(discovery_info)
|
|
|
|
|
|
|
|
async def _async_discovery_handler(self, discovery_info=None):
|
|
|
|
"""Test any discovery handler."""
|
|
|
|
await self.async_set_unique_id("thisid")
|
|
|
|
self._abort_if_unique_id_configured()
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
return await self.async_step_link()
|
|
|
|
|
|
|
|
async def async_step_link(self, user_input=None):
|
|
|
|
"""Test a link step."""
|
|
|
|
if user_input is None:
|
|
|
|
return self.async_show_form(step_id="link")
|
|
|
|
return self.async_create_entry(title="title", data={"token": "supersecret"})
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow}):
|
|
|
|
# Create one to be in progress
|
|
|
|
flow1 = manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_ZEROCONF}
|
|
|
|
)
|
|
|
|
flow2 = manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_ZEROCONF}
|
|
|
|
)
|
|
|
|
flow3 = manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_HOMEKIT}
|
|
|
|
)
|
|
|
|
result1, result2, result3 = await asyncio.gather(flow1, flow2, flow3)
|
|
|
|
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
assert flows[0]["context"]["unique_id"] == "thisid"
|
|
|
|
|
|
|
|
# Finish flow
|
|
|
|
result2 = await manager.flow.async_configure(
|
|
|
|
flows[0]["flow_id"], user_input={"fake": "data"}
|
|
|
|
)
|
2022-07-07 16:57:36 +00:00
|
|
|
assert result2["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
2021-08-24 04:01:21 +00:00
|
|
|
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 0
|
|
|
|
|
|
|
|
entry = hass.config_entries.async_entries("comp")[0]
|
|
|
|
assert entry.title == "title"
|
|
|
|
assert entry.source in {
|
|
|
|
config_entries.SOURCE_ZEROCONF,
|
|
|
|
config_entries.SOURCE_HOMEKIT,
|
|
|
|
}
|
|
|
|
assert entry.unique_id == "thisid"
|
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_updating_entry_with_and_without_changes(
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
) -> None:
|
2020-08-08 18:23:56 +00:00
|
|
|
"""Test that we can update an entry data."""
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="test",
|
|
|
|
data={"first": True},
|
|
|
|
title="thetitle",
|
|
|
|
options={"option": True},
|
|
|
|
unique_id="abc123",
|
2021-05-20 17:19:20 +00:00
|
|
|
state=config_entries.ConfigEntryState.SETUP_ERROR,
|
2020-08-08 18:23:56 +00:00
|
|
|
)
|
|
|
|
entry.add_to_manager(manager)
|
2024-01-13 20:34:15 +00:00
|
|
|
assert "abc123" in str(entry)
|
|
|
|
|
|
|
|
assert manager.async_entry_for_domain_unique_id("test", "abc123") is entry
|
2020-08-08 18:23:56 +00:00
|
|
|
|
|
|
|
assert manager.async_update_entry(entry) is False
|
2021-06-01 20:34:31 +00:00
|
|
|
|
|
|
|
for change in (
|
|
|
|
{"data": {"second": True, "third": 456}},
|
|
|
|
{"data": {"second": True}},
|
2024-02-09 09:10:25 +00:00
|
|
|
{"minor_version": 2},
|
2021-06-01 20:34:31 +00:00
|
|
|
{"options": {"hello": True}},
|
|
|
|
{"pref_disable_new_entities": True},
|
|
|
|
{"pref_disable_polling": True},
|
|
|
|
{"title": "sometitle"},
|
|
|
|
{"unique_id": "abcd1234"},
|
2024-02-09 09:10:25 +00:00
|
|
|
{"version": 2},
|
2021-06-01 20:34:31 +00:00
|
|
|
):
|
|
|
|
assert manager.async_update_entry(entry, **change) is True
|
2024-02-09 09:10:25 +00:00
|
|
|
key = next(iter(change))
|
|
|
|
value = next(iter(change.values()))
|
|
|
|
assert getattr(entry, key) == value
|
2021-06-01 20:34:31 +00:00
|
|
|
assert manager.async_update_entry(entry, **change) is False
|
2021-04-09 17:14:33 +00:00
|
|
|
|
2024-01-13 20:34:15 +00:00
|
|
|
assert manager.async_entry_for_domain_unique_id("test", "abc123") is None
|
|
|
|
assert manager.async_entry_for_domain_unique_id("test", "abcd1234") is entry
|
|
|
|
assert "abcd1234" in str(entry)
|
|
|
|
|
2021-04-09 17:14:33 +00:00
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_entry_reload_calls_on_unload_listeners(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-04-09 17:14:33 +00:00
|
|
|
"""Test reload calls the on unload listeners."""
|
2021-05-20 17:19:20 +00:00
|
|
|
entry = MockConfigEntry(domain="comp", state=config_entries.ConfigEntryState.LOADED)
|
2021-04-09 17:14:33 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
async_unload_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2024-04-30 23:47:12 +00:00
|
|
|
hass.config.components.add("comp")
|
2021-04-09 17:14:33 +00:00
|
|
|
|
|
|
|
mock_unload_callback = Mock()
|
|
|
|
|
|
|
|
entry.async_on_unload(mock_unload_callback)
|
|
|
|
|
|
|
|
assert await manager.async_reload(entry.entry_id)
|
|
|
|
assert len(async_unload_entry.mock_calls) == 1
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 1
|
|
|
|
assert len(mock_unload_callback.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2021-04-09 17:14:33 +00:00
|
|
|
|
|
|
|
assert await manager.async_reload(entry.entry_id)
|
|
|
|
assert len(async_unload_entry.mock_calls) == 2
|
|
|
|
assert len(mock_setup_entry.mock_calls) == 2
|
|
|
|
# Since we did not register another async_on_unload it should
|
|
|
|
# have only been called once
|
|
|
|
assert len(mock_unload_callback.mock_calls) == 1
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2021-04-09 17:14:33 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_setup_raise_entry_error(
|
2024-05-11 23:20:08 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2022-11-25 10:33:03 +00:00
|
|
|
"""Test a setup raising ConfigEntryError."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2022-11-25 10:33:03 +00:00
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(
|
|
|
|
side_effect=ConfigEntryError("Incompatible firmware version")
|
|
|
|
)
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2022-11-25 10:33:03 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2022-11-25 10:33:03 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert (
|
|
|
|
"Error setting up entry test_title for test: Incompatible firmware version"
|
|
|
|
in caplog.text
|
|
|
|
)
|
|
|
|
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR
|
|
|
|
assert entry.reason == "Incompatible firmware version"
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_setup_raise_entry_error_from_first_coordinator_update(
|
2024-05-11 23:20:08 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2022-11-25 10:33:03 +00:00
|
|
|
"""Test async_config_entry_first_refresh raises ConfigEntryError."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2022-11-25 10:33:03 +00:00
|
|
|
|
|
|
|
async def async_setup_entry(hass, entry):
|
|
|
|
"""Mock setup entry with a simple coordinator."""
|
|
|
|
|
|
|
|
async def _async_update_data():
|
|
|
|
raise ConfigEntryError("Incompatible firmware version")
|
|
|
|
|
|
|
|
coordinator = DataUpdateCoordinator(
|
|
|
|
hass,
|
|
|
|
logging.getLogger(__name__),
|
|
|
|
name="any",
|
|
|
|
update_method=_async_update_data,
|
|
|
|
update_interval=timedelta(seconds=1000),
|
|
|
|
)
|
|
|
|
|
|
|
|
await coordinator.async_config_entry_first_refresh()
|
|
|
|
return True
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=async_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2022-11-25 10:33:03 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2022-11-25 10:33:03 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert (
|
|
|
|
"Error setting up entry test_title for test: Incompatible firmware version"
|
|
|
|
in caplog.text
|
|
|
|
)
|
|
|
|
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR
|
|
|
|
assert entry.reason == "Incompatible firmware version"
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_setup_not_raise_entry_error_from_future_coordinator_update(
|
2024-05-11 23:20:08 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2022-11-25 10:33:03 +00:00
|
|
|
"""Test a coordinator not raises ConfigEntryError in the future."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2022-11-25 10:33:03 +00:00
|
|
|
|
|
|
|
async def async_setup_entry(hass, entry):
|
|
|
|
"""Mock setup entry with a simple coordinator."""
|
|
|
|
|
|
|
|
async def _async_update_data():
|
|
|
|
raise ConfigEntryError("Incompatible firmware version")
|
|
|
|
|
|
|
|
coordinator = DataUpdateCoordinator(
|
|
|
|
hass,
|
|
|
|
logging.getLogger(__name__),
|
|
|
|
name="any",
|
|
|
|
update_method=_async_update_data,
|
|
|
|
update_interval=timedelta(seconds=1000),
|
|
|
|
)
|
|
|
|
|
|
|
|
await coordinator.async_refresh()
|
|
|
|
return True
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=async_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2022-11-25 10:33:03 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2022-11-25 10:33:03 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert (
|
2023-01-20 12:52:46 +00:00
|
|
|
"Config entry setup failed while fetching any data: Incompatible firmware"
|
|
|
|
" version" in caplog.text
|
2022-11-25 10:33:03 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_setup_raise_auth_failed(
|
2024-05-11 23:20:08 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-04-10 05:41:29 +00:00
|
|
|
"""Test a setup raising ConfigEntryAuthFailed."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2021-04-10 05:41:29 +00:00
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(
|
|
|
|
side_effect=ConfigEntryAuthFailed("The password is no longer valid")
|
|
|
|
)
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2021-04-10 05:41:29 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2021-04-10 05:41:29 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert "could not authenticate: The password is no longer valid" in caplog.text
|
|
|
|
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR
|
2021-04-23 07:23:43 +00:00
|
|
|
assert entry.reason == "The password is no longer valid"
|
2021-04-10 05:41:29 +00:00
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
assert flows[0]["context"]["entry_id"] == entry.entry_id
|
|
|
|
assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH
|
2022-01-18 22:18:16 +00:00
|
|
|
assert flows[0]["context"]["title_placeholders"] == {"name": "test_title"}
|
2021-04-10 05:41:29 +00:00
|
|
|
|
|
|
|
caplog.clear()
|
2024-02-16 16:15:05 +00:00
|
|
|
entry._async_set_state(hass, config_entries.ConfigEntryState.NOT_LOADED, None)
|
2021-04-10 05:41:29 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2021-04-10 05:41:29 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert "could not authenticate: The password is no longer valid" in caplog.text
|
|
|
|
|
|
|
|
# Verify multiple ConfigEntryAuthFailed does not generate a second flow
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR
|
2021-04-10 05:41:29 +00:00
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_setup_raise_auth_failed_from_first_coordinator_update(
|
2024-05-11 23:20:08 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-04-10 05:41:29 +00:00
|
|
|
"""Test async_config_entry_first_refresh raises ConfigEntryAuthFailed."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2021-04-10 05:41:29 +00:00
|
|
|
|
|
|
|
async def async_setup_entry(hass, entry):
|
|
|
|
"""Mock setup entry with a simple coordinator."""
|
2021-04-09 17:14:33 +00:00
|
|
|
|
2021-04-10 05:41:29 +00:00
|
|
|
async def _async_update_data():
|
|
|
|
raise ConfigEntryAuthFailed("The password is no longer valid")
|
|
|
|
|
|
|
|
coordinator = DataUpdateCoordinator(
|
|
|
|
hass,
|
|
|
|
logging.getLogger(__name__),
|
|
|
|
name="any",
|
|
|
|
update_method=_async_update_data,
|
|
|
|
update_interval=timedelta(seconds=1000),
|
|
|
|
)
|
|
|
|
|
|
|
|
await coordinator.async_config_entry_first_refresh()
|
2021-04-09 17:14:33 +00:00
|
|
|
return True
|
|
|
|
|
2021-04-10 05:41:29 +00:00
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=async_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2021-04-09 17:14:33 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2021-04-10 05:41:29 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert "could not authenticate: The password is no longer valid" in caplog.text
|
2021-04-09 17:14:33 +00:00
|
|
|
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR
|
2021-04-10 05:41:29 +00:00
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
assert flows[0]["context"]["entry_id"] == entry.entry_id
|
|
|
|
assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH
|
2021-04-09 17:14:33 +00:00
|
|
|
|
2021-04-10 05:41:29 +00:00
|
|
|
caplog.clear()
|
2024-02-16 16:15:05 +00:00
|
|
|
entry._async_set_state(hass, config_entries.ConfigEntryState.NOT_LOADED, None)
|
2021-04-09 17:14:33 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2021-04-10 05:41:29 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert "could not authenticate: The password is no longer valid" in caplog.text
|
2021-04-09 17:14:33 +00:00
|
|
|
|
2021-04-10 05:41:29 +00:00
|
|
|
# Verify multiple ConfigEntryAuthFailed does not generate a second flow
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_ERROR
|
2021-04-10 05:41:29 +00:00
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_setup_raise_auth_failed_from_future_coordinator_update(
|
2024-05-11 23:20:08 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-04-10 05:41:29 +00:00
|
|
|
"""Test a coordinator raises ConfigEntryAuthFailed in the future."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2021-04-10 05:41:29 +00:00
|
|
|
|
|
|
|
async def async_setup_entry(hass, entry):
|
|
|
|
"""Mock setup entry with a simple coordinator."""
|
|
|
|
|
|
|
|
async def _async_update_data():
|
|
|
|
raise ConfigEntryAuthFailed("The password is no longer valid")
|
|
|
|
|
|
|
|
coordinator = DataUpdateCoordinator(
|
|
|
|
hass,
|
|
|
|
logging.getLogger(__name__),
|
|
|
|
name="any",
|
|
|
|
update_method=_async_update_data,
|
|
|
|
update_interval=timedelta(seconds=1000),
|
|
|
|
)
|
|
|
|
|
|
|
|
await coordinator.async_refresh()
|
|
|
|
return True
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=async_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2021-04-10 05:41:29 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2021-04-10 05:41:29 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert "Authentication failed while fetching" in caplog.text
|
|
|
|
assert "The password is no longer valid" in caplog.text
|
2021-04-09 17:14:33 +00:00
|
|
|
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2021-04-10 05:41:29 +00:00
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
assert flows[0]["context"]["entry_id"] == entry.entry_id
|
|
|
|
assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH
|
2021-04-09 17:14:33 +00:00
|
|
|
|
2021-04-10 05:41:29 +00:00
|
|
|
caplog.clear()
|
2024-02-16 16:15:05 +00:00
|
|
|
entry._async_set_state(hass, config_entries.ConfigEntryState.NOT_LOADED, None)
|
2021-04-10 05:41:29 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2021-04-10 05:41:29 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert "Authentication failed while fetching" in caplog.text
|
|
|
|
assert "The password is no longer valid" in caplog.text
|
|
|
|
|
|
|
|
# Verify multiple ConfigEntryAuthFailed does not generate a second flow
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2021-04-10 05:41:29 +00:00
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
2021-04-14 02:16:26 +00:00
|
|
|
|
|
|
|
|
2023-02-08 07:51:43 +00:00
|
|
|
async def test_initialize_and_shutdown(hass: HomeAssistant) -> None:
|
2021-04-14 02:16:26 +00:00
|
|
|
"""Test we call the shutdown function at stop."""
|
|
|
|
manager = config_entries.ConfigEntries(hass, {})
|
|
|
|
|
|
|
|
with patch.object(manager, "_async_shutdown") as mock_async_shutdown:
|
|
|
|
await manager.async_initialize()
|
|
|
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert mock_async_shutdown.called
|
|
|
|
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
async def test_setup_retrying_during_shutdown(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2021-04-14 02:16:26 +00:00
|
|
|
"""Test if we shutdown an entry that is in retry mode."""
|
|
|
|
entry = MockConfigEntry(domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2021-04-14 02:16:26 +00:00
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(side_effect=ConfigEntryNotReady)
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2021-04-14 02:16:26 +00:00
|
|
|
|
|
|
|
with patch("homeassistant.helpers.event.async_call_later") as mock_call:
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2021-04-14 02:16:26 +00:00
|
|
|
|
2021-05-20 17:19:20 +00:00
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
|
2021-04-14 02:16:26 +00:00
|
|
|
assert len(mock_call.return_value.mock_calls) == 0
|
|
|
|
|
|
|
|
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(mock_call.return_value.mock_calls) == 0
|
|
|
|
|
2023-05-30 20:30:31 +00:00
|
|
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(hours=4))
|
2021-04-14 02:16:26 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(mock_call.return_value.mock_calls) == 0
|
2021-05-11 20:00:12 +00:00
|
|
|
|
2023-03-16 10:07:42 +00:00
|
|
|
# Cleanup to avoid lingering timer
|
|
|
|
entry.async_cancel_retry_setup()
|
|
|
|
|
2021-05-11 20:00:12 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
async def test_scheduling_reload_cancels_setup_retry(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2024-02-20 01:14:45 +00:00
|
|
|
"""Test scheduling a reload cancels setup retry."""
|
|
|
|
entry = MockConfigEntry(domain="test")
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(side_effect=ConfigEntryNotReady)
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
|
|
|
mock_platform(hass, "test.config_flow", None)
|
|
|
|
cancel_mock = Mock()
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
"homeassistant.config_entries.async_call_later", return_value=cancel_mock
|
|
|
|
):
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2024-02-20 01:14:45 +00:00
|
|
|
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_RETRY
|
|
|
|
assert len(cancel_mock.mock_calls) == 0
|
|
|
|
|
|
|
|
mock_setup_entry.side_effect = None
|
|
|
|
mock_setup_entry.return_value = True
|
|
|
|
hass.config_entries.async_schedule_reload(entry.entry_id)
|
|
|
|
|
|
|
|
assert len(cancel_mock.mock_calls) == 1
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
|
|
|
|
|
|
|
|
|
|
|
async def test_scheduling_reload_unknown_entry(hass: HomeAssistant) -> None:
|
|
|
|
"""Test scheduling a reload raises with an unknown entry."""
|
|
|
|
with pytest.raises(config_entries.UnknownEntry):
|
|
|
|
hass.config_entries.async_schedule_reload("non-existing")
|
|
|
|
|
|
|
|
|
2021-05-11 20:00:12 +00:00
|
|
|
@pytest.mark.parametrize(
|
2023-02-15 13:09:50 +00:00
|
|
|
("matchers", "reason"),
|
2021-05-11 20:00:12 +00:00
|
|
|
[
|
|
|
|
({}, "already_configured"),
|
|
|
|
({"host": "3.3.3.3"}, "no_match"),
|
2022-02-16 17:15:31 +00:00
|
|
|
({"vendor": "no_match"}, "no_match"),
|
2021-05-11 20:00:12 +00:00
|
|
|
({"host": "3.4.5.6"}, "already_configured"),
|
|
|
|
({"host": "3.4.5.6", "ip": "3.4.5.6"}, "no_match"),
|
|
|
|
({"host": "3.4.5.6", "ip": "1.2.3.4"}, "already_configured"),
|
|
|
|
({"host": "3.4.5.6", "ip": "1.2.3.4", "port": 23}, "already_configured"),
|
2022-02-16 17:15:31 +00:00
|
|
|
(
|
|
|
|
{"host": "9.9.9.9", "ip": "6.6.6.6", "port": 12, "vendor": "zoo"},
|
|
|
|
"already_configured",
|
|
|
|
),
|
|
|
|
({"vendor": "zoo"}, "already_configured"),
|
2021-05-11 20:00:12 +00:00
|
|
|
({"ip": "9.9.9.9"}, "already_configured"),
|
|
|
|
({"ip": "7.7.7.7"}, "no_match"), # ignored
|
2023-08-24 13:34:45 +00:00
|
|
|
# The next two data sets ensure options or data match
|
|
|
|
# as options previously shadowed data when matching.
|
|
|
|
({"vendor": "data"}, "already_configured"),
|
2022-02-16 17:15:31 +00:00
|
|
|
(
|
|
|
|
{"vendor": "options"},
|
|
|
|
"already_configured",
|
2023-08-24 13:34:45 +00:00
|
|
|
),
|
2021-05-11 20:00:12 +00:00
|
|
|
],
|
|
|
|
)
|
2024-05-11 19:45:03 +00:00
|
|
|
async def test_async_abort_entries_match(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
matchers: dict[str, str],
|
|
|
|
reason: str,
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-05-11 20:00:12 +00:00
|
|
|
"""Test aborting if matching config entries exist."""
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="comp", data={"ip": "1.2.3.4", "host": "4.5.6.7", "port": 23}
|
|
|
|
).add_to_hass(hass)
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="comp", data={"ip": "9.9.9.9", "host": "4.5.6.7", "port": 23}
|
|
|
|
).add_to_hass(hass)
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="comp", data={"ip": "1.2.3.4", "host": "3.4.5.6", "port": 23}
|
|
|
|
).add_to_hass(hass)
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
source=config_entries.SOURCE_IGNORE,
|
|
|
|
data={"ip": "7.7.7.7", "host": "4.5.6.7", "port": 23},
|
|
|
|
).add_to_hass(hass)
|
2022-02-16 17:15:31 +00:00
|
|
|
MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
data={"ip": "6.6.6.6", "host": "9.9.9.9", "port": 12},
|
|
|
|
options={"vendor": "zoo"},
|
|
|
|
).add_to_hass(hass)
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
data={"vendor": "data"},
|
|
|
|
options={"vendor": "options"},
|
|
|
|
).add_to_hass(hass)
|
2021-05-11 20:00:12 +00:00
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("comp", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2021-05-11 20:00:12 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
|
|
|
"""Test user step."""
|
|
|
|
self._async_abort_entries_match(matchers)
|
|
|
|
return self.async_abort(reason="no_match")
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": TestFlow, "beer": 5}):
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2022-07-07 19:28:18 +00:00
|
|
|
assert result["type"] == FlowResultType.ABORT
|
2021-05-11 20:00:12 +00:00
|
|
|
assert result["reason"] == reason
|
2021-06-01 20:34:31 +00:00
|
|
|
|
|
|
|
|
2023-03-29 15:20:51 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
("matchers", "reason"),
|
|
|
|
[
|
|
|
|
({}, "already_configured"),
|
|
|
|
({"host": "3.3.3.3"}, "no_match"),
|
|
|
|
({"vendor": "no_match"}, "no_match"),
|
|
|
|
({"host": "3.4.5.6"}, "already_configured"),
|
|
|
|
({"host": "3.4.5.6", "ip": "3.4.5.6"}, "no_match"),
|
|
|
|
({"host": "3.4.5.6", "ip": "1.2.3.4"}, "already_configured"),
|
|
|
|
({"host": "3.4.5.6", "ip": "1.2.3.4", "port": 23}, "already_configured"),
|
|
|
|
(
|
|
|
|
{"host": "9.9.9.9", "ip": "6.6.6.6", "port": 12, "vendor": "zoo"},
|
|
|
|
"already_configured",
|
|
|
|
),
|
|
|
|
({"vendor": "zoo"}, "already_configured"),
|
|
|
|
({"ip": "9.9.9.9"}, "already_configured"),
|
|
|
|
({"ip": "7.7.7.7"}, "no_match"), # ignored
|
2023-08-24 13:34:45 +00:00
|
|
|
# The next two data sets ensure options or data match
|
|
|
|
# as options previously shadowed data when matching.
|
|
|
|
({"vendor": "data"}, "already_configured"),
|
2023-03-29 15:20:51 +00:00
|
|
|
(
|
|
|
|
{"vendor": "options"},
|
|
|
|
"already_configured",
|
2023-08-24 13:34:45 +00:00
|
|
|
),
|
2023-03-29 15:20:51 +00:00
|
|
|
],
|
|
|
|
)
|
2024-05-11 19:45:03 +00:00
|
|
|
async def test_async_abort_entries_match_options_flow(
|
2023-03-29 15:20:51 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
matchers: dict[str, str],
|
|
|
|
reason: str,
|
|
|
|
) -> None:
|
|
|
|
"""Test aborting if matching config entries exist."""
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="test_abort", data={"ip": "1.2.3.4", "host": "4.5.6.7", "port": 23}
|
|
|
|
).add_to_hass(hass)
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="test_abort", data={"ip": "9.9.9.9", "host": "4.5.6.7", "port": 23}
|
|
|
|
).add_to_hass(hass)
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="test_abort", data={"ip": "1.2.3.4", "host": "3.4.5.6", "port": 23}
|
|
|
|
).add_to_hass(hass)
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="test_abort",
|
|
|
|
source=config_entries.SOURCE_IGNORE,
|
|
|
|
data={"ip": "7.7.7.7", "host": "4.5.6.7", "port": 23},
|
|
|
|
).add_to_hass(hass)
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="test_abort",
|
|
|
|
data={"ip": "6.6.6.6", "host": "9.9.9.9", "port": 12},
|
|
|
|
options={"vendor": "zoo"},
|
|
|
|
).add_to_hass(hass)
|
|
|
|
MockConfigEntry(
|
|
|
|
domain="test_abort",
|
|
|
|
data={"vendor": "data"},
|
|
|
|
options={"vendor": "options"},
|
|
|
|
).add_to_hass(hass)
|
|
|
|
|
|
|
|
original_entry = MockConfigEntry(domain="test_abort", data={})
|
|
|
|
original_entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("test_abort", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test_abort.config_flow", None)
|
2023-03-29 15:20:51 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@callback
|
|
|
|
def async_get_options_flow(config_entry):
|
|
|
|
"""Test options flow."""
|
|
|
|
|
|
|
|
class _OptionsFlow(config_entries.OptionsFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
async def async_step_init(self, user_input=None):
|
|
|
|
"""Test user step."""
|
|
|
|
if errors := self._async_abort_entries_match(user_input):
|
|
|
|
return self.async_abort(reason=errors["base"])
|
|
|
|
return self.async_abort(reason="no_match")
|
|
|
|
|
|
|
|
return _OptionsFlow()
|
|
|
|
|
|
|
|
with mock_config_flow("test_abort", TestFlow):
|
|
|
|
result = await hass.config_entries.options.async_init(
|
|
|
|
original_entry.entry_id, data=matchers
|
|
|
|
)
|
|
|
|
|
|
|
|
assert result["type"] == FlowResultType.ABORT
|
|
|
|
assert result["reason"] == reason
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_loading_old_data(
|
|
|
|
hass: HomeAssistant, hass_storage: dict[str, Any]
|
|
|
|
) -> None:
|
2021-06-01 20:34:31 +00:00
|
|
|
"""Test automatically migrating old data."""
|
|
|
|
hass_storage[config_entries.STORAGE_KEY] = {
|
|
|
|
"version": 1,
|
|
|
|
"data": {
|
|
|
|
"entries": [
|
|
|
|
{
|
|
|
|
"version": 5,
|
|
|
|
"domain": "my_domain",
|
|
|
|
"entry_id": "mock-id",
|
|
|
|
"data": {"my": "data"},
|
|
|
|
"source": "user",
|
|
|
|
"title": "Mock title",
|
|
|
|
"system_options": {"disable_new_entities": True},
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
}
|
|
|
|
manager = config_entries.ConfigEntries(hass, {})
|
|
|
|
await manager.async_initialize()
|
|
|
|
|
|
|
|
entries = manager.async_entries()
|
|
|
|
assert len(entries) == 1
|
|
|
|
entry = entries[0]
|
|
|
|
assert entry.version == 5
|
|
|
|
assert entry.domain == "my_domain"
|
|
|
|
assert entry.entry_id == "mock-id"
|
|
|
|
assert entry.title == "Mock title"
|
|
|
|
assert entry.data == {"my": "data"}
|
|
|
|
assert entry.pref_disable_new_entities is True
|
2021-12-15 19:53:21 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_deprecated_disabled_by_str_ctor(
|
|
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
|
|
|
) -> None:
|
2021-12-15 19:53:21 +00:00
|
|
|
"""Test deprecated str disabled_by constructor enumizes and logs a warning."""
|
|
|
|
entry = MockConfigEntry(disabled_by=config_entries.ConfigEntryDisabler.USER.value)
|
|
|
|
assert entry.disabled_by is config_entries.ConfigEntryDisabler.USER
|
|
|
|
assert " str for config entry disabled_by. This is deprecated " in caplog.text
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_deprecated_disabled_by_str_set(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2021-12-15 19:53:21 +00:00
|
|
|
"""Test deprecated str set disabled_by enumizes and logs a warning."""
|
2024-04-30 23:47:12 +00:00
|
|
|
entry = MockConfigEntry(domain="comp")
|
2021-12-15 19:53:21 +00:00
|
|
|
entry.add_to_manager(manager)
|
2024-04-30 23:47:12 +00:00
|
|
|
hass.config.components.add("comp")
|
2021-12-15 19:53:21 +00:00
|
|
|
assert await manager.async_set_disabled_by(
|
|
|
|
entry.entry_id, config_entries.ConfigEntryDisabler.USER.value
|
|
|
|
)
|
|
|
|
assert entry.disabled_by is config_entries.ConfigEntryDisabler.USER
|
|
|
|
assert " str for config entry disabled_by. This is deprecated " in caplog.text
|
2022-05-28 08:49:55 +00:00
|
|
|
|
|
|
|
|
2023-03-16 10:08:47 +00:00
|
|
|
async def test_entry_reload_concurrency(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2022-05-28 08:49:55 +00:00
|
|
|
"""Test multiple reload calls do not cause a reload race."""
|
|
|
|
entry = MockConfigEntry(domain="comp", state=config_entries.ConfigEntryState.LOADED)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
loaded = 1
|
|
|
|
|
2024-04-30 23:47:12 +00:00
|
|
|
async def _async_setup_entry(*args, **kwargs):
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
nonlocal loaded
|
|
|
|
loaded += 1
|
|
|
|
return loaded == 1
|
|
|
|
|
|
|
|
async def _async_unload_entry(*args, **kwargs):
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
nonlocal loaded
|
|
|
|
loaded -= 1
|
|
|
|
return loaded == 0
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=_async_setup_entry,
|
|
|
|
async_unload_entry=_async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
tasks = [
|
|
|
|
asyncio.create_task(manager.async_reload(entry.entry_id)) for _ in range(15)
|
|
|
|
]
|
|
|
|
await asyncio.gather(*tasks)
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
|
|
|
assert loaded == 1
|
|
|
|
|
|
|
|
|
|
|
|
async def test_entry_reload_concurrency_not_setup_setup(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
|
|
|
"""Test multiple reload calls do not cause a reload race."""
|
2024-05-10 22:09:28 +00:00
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", state=config_entries.ConfigEntryState.NOT_LOADED
|
|
|
|
)
|
2024-04-30 23:47:12 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
async_setup = AsyncMock(return_value=True)
|
|
|
|
loaded = 0
|
|
|
|
|
2022-05-28 08:49:55 +00:00
|
|
|
async def _async_setup_entry(*args, **kwargs):
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
nonlocal loaded
|
|
|
|
loaded += 1
|
|
|
|
return loaded == 1
|
|
|
|
|
|
|
|
async def _async_unload_entry(*args, **kwargs):
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
nonlocal loaded
|
|
|
|
loaded -= 1
|
|
|
|
return loaded == 0
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup=async_setup,
|
|
|
|
async_setup_entry=_async_setup_entry,
|
|
|
|
async_unload_entry=_async_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2024-03-14 09:22:20 +00:00
|
|
|
tasks = [
|
|
|
|
asyncio.create_task(manager.async_reload(entry.entry_id)) for _ in range(15)
|
|
|
|
]
|
2022-05-28 08:49:55 +00:00
|
|
|
await asyncio.gather(*tasks)
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
|
|
|
assert loaded == 1
|
2022-06-07 05:48:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_unique_id_update_while_setup_in_progress(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
|
|
|
"""Test we handle the case where the config entry is updated while setup is in progress."""
|
|
|
|
|
|
|
|
async def mock_setup_entry(hass, entry):
|
|
|
|
"""Mock setting up entry."""
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def mock_unload_entry(hass, entry):
|
|
|
|
"""Mock unloading an entry."""
|
|
|
|
return True
|
|
|
|
|
|
|
|
hass.config.components.add("comp")
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
data={"additional": "data", "host": "0.0.0.0"},
|
|
|
|
unique_id="mock-unique-id",
|
|
|
|
state=config_entries.ConfigEntryState.SETUP_RETRY,
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=mock_unload_entry,
|
|
|
|
),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2022-06-07 05:48:49 +00:00
|
|
|
updates = {"host": "1.1.1.1"}
|
|
|
|
|
|
|
|
hass.async_create_task(hass.config_entries.async_reload(entry.entry_id))
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_IN_PROGRESS
|
|
|
|
|
|
|
|
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")
|
|
|
|
await self._abort_if_unique_id_configured(
|
|
|
|
updates=updates, reload_on_update=True
|
|
|
|
)
|
|
|
|
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
patch(
|
|
|
|
"homeassistant.config_entries.ConfigEntries.async_reload"
|
|
|
|
) as async_reload,
|
|
|
|
):
|
2022-06-07 05:48:49 +00:00
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2022-07-07 19:28:18 +00:00
|
|
|
assert result["type"] == FlowResultType.ABORT
|
2022-06-07 05:48:49 +00:00
|
|
|
assert result["reason"] == "already_configured"
|
|
|
|
assert entry.data["host"] == "1.1.1.1"
|
|
|
|
assert entry.data["additional"] == "data"
|
|
|
|
|
|
|
|
# Setup is already in progress, we should not reload
|
|
|
|
# if it fails it will go into a retry state and try again
|
|
|
|
assert len(async_reload.mock_calls) == 0
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
2022-06-13 06:05:08 +00:00
|
|
|
|
|
|
|
|
2024-02-20 01:14:45 +00:00
|
|
|
async def test_disallow_entry_reload_with_setup_in_progress(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2022-06-13 06:05:08 +00:00
|
|
|
"""Test we do not allow reload while the config entry is still setting up."""
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp", state=config_entries.ConfigEntryState.SETUP_IN_PROGRESS
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
2024-04-30 23:47:12 +00:00
|
|
|
hass.config.components.add("comp")
|
2022-06-13 06:05:08 +00:00
|
|
|
|
2022-09-17 17:52:28 +00:00
|
|
|
with pytest.raises(
|
|
|
|
config_entries.OperationNotAllowed,
|
|
|
|
match=str(config_entries.ConfigEntryState.SETUP_IN_PROGRESS),
|
|
|
|
):
|
2022-06-13 06:05:08 +00:00
|
|
|
assert await manager.async_reload(entry.entry_id)
|
|
|
|
assert entry.state is config_entries.ConfigEntryState.SETUP_IN_PROGRESS
|
2022-08-02 16:20:37 +00:00
|
|
|
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
async def test_reauth(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2022-08-02 16:20:37 +00:00
|
|
|
"""Test the async_reauth_helper."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2022-11-08 03:19:57 +00:00
|
|
|
entry2 = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry2.add_to_hass(hass)
|
2022-08-02 16:20:37 +00:00
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2022-08-02 16:20:37 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2022-08-02 16:20:37 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2022-08-02 20:38:38 +00:00
|
|
|
flow = hass.config_entries.flow
|
|
|
|
with patch.object(flow, "async_init", wraps=flow.async_init) as mock_init:
|
|
|
|
entry.async_start_reauth(
|
|
|
|
hass,
|
|
|
|
context={"extra_context": "some_extra_context"},
|
|
|
|
data={"extra_data": 1234},
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
2022-08-02 16:20:37 +00:00
|
|
|
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
assert flows[0]["context"]["entry_id"] == entry.entry_id
|
|
|
|
assert flows[0]["context"]["source"] == config_entries.SOURCE_REAUTH
|
|
|
|
assert flows[0]["context"]["title_placeholders"] == {"name": "test_title"}
|
|
|
|
assert flows[0]["context"]["extra_context"] == "some_extra_context"
|
|
|
|
|
2022-08-02 20:38:38 +00:00
|
|
|
assert mock_init.call_args.kwargs["data"]["extra_data"] == 1234
|
|
|
|
|
2022-11-08 03:19:57 +00:00
|
|
|
assert entry.entry_id != entry2.entry_id
|
|
|
|
|
2022-11-09 15:36:46 +00:00
|
|
|
# Check that we can't start duplicate reauth flows
|
2022-08-02 16:20:37 +00:00
|
|
|
entry.async_start_reauth(hass, {"extra_context": "some_extra_context"})
|
|
|
|
await hass.async_block_till_done()
|
2022-11-08 03:19:57 +00:00
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
|
2022-11-09 15:36:46 +00:00
|
|
|
# Check that we can't start duplicate reauth flows when the context is different
|
2022-11-08 03:19:57 +00:00
|
|
|
entry.async_start_reauth(hass, {"diff": "diff"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
|
2022-11-09 15:36:46 +00:00
|
|
|
# Check that we can start a reauth flow for a different entry
|
2022-11-08 03:19:57 +00:00
|
|
|
entry2.async_start_reauth(hass, {"extra_context": "some_extra_context"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 2
|
2022-11-08 16:18:40 +00:00
|
|
|
|
2023-10-31 17:38:05 +00:00
|
|
|
# Abort all existing flows
|
|
|
|
for flow in hass.config_entries.flow.async_progress():
|
|
|
|
hass.config_entries.flow.async_abort(flow["flow_id"])
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check that we can't start duplicate reauth flows
|
|
|
|
# without blocking between flows
|
|
|
|
entry.async_start_reauth(hass, {"extra_context": "some_extra_context"})
|
|
|
|
entry.async_start_reauth(hass, {"extra_context": "some_extra_context"})
|
|
|
|
entry.async_start_reauth(hass, {"extra_context": "some_extra_context"})
|
|
|
|
entry.async_start_reauth(hass, {"extra_context": "some_extra_context"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
|
2022-11-08 16:18:40 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
async def test_reconfigure(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2024-03-01 11:29:35 +00:00
|
|
|
"""Test the async_reconfigure_helper."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2024-03-01 11:29:35 +00:00
|
|
|
entry2 = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry2.add_to_hass(hass)
|
2024-03-01 11:29:35 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2024-03-01 11:29:35 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
flow = hass.config_entries.flow
|
|
|
|
with patch.object(flow, "async_init", wraps=flow.async_init) as mock_init:
|
|
|
|
entry.async_start_reconfigure(
|
|
|
|
hass,
|
|
|
|
context={"extra_context": "some_extra_context"},
|
|
|
|
data={"extra_data": 1234},
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
flows = hass.config_entries.flow.async_progress()
|
|
|
|
assert len(flows) == 1
|
|
|
|
assert flows[0]["context"]["entry_id"] == entry.entry_id
|
|
|
|
assert flows[0]["context"]["source"] == config_entries.SOURCE_RECONFIGURE
|
|
|
|
assert flows[0]["context"]["title_placeholders"] == {"name": "test_title"}
|
|
|
|
assert flows[0]["context"]["extra_context"] == "some_extra_context"
|
|
|
|
|
|
|
|
assert mock_init.call_args.kwargs["data"]["extra_data"] == 1234
|
|
|
|
|
|
|
|
assert entry.entry_id != entry2.entry_id
|
|
|
|
|
|
|
|
# Check that we can't start duplicate reconfigure flows
|
|
|
|
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
|
|
|
|
# Check that we can't start duplicate reconfigure flows when the context is different
|
|
|
|
entry.async_start_reconfigure(hass, {"diff": "diff"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
|
|
|
|
# Check that we can start a reconfigure flow for a different entry
|
|
|
|
entry2.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 2
|
|
|
|
|
|
|
|
# Abort all existing flows
|
|
|
|
for flow in hass.config_entries.flow.async_progress():
|
|
|
|
hass.config_entries.flow.async_abort(flow["flow_id"])
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check that we can't start duplicate reconfigure flows
|
|
|
|
# without blocking between flows
|
|
|
|
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
|
|
|
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
|
|
|
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
|
|
|
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
|
|
|
|
# Abort all existing flows
|
|
|
|
for flow in hass.config_entries.flow.async_progress():
|
|
|
|
hass.config_entries.flow.async_abort(flow["flow_id"])
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check that we can't start reconfigure flows with active reauth flow
|
|
|
|
entry.async_start_reauth(hass, {"extra_context": "some_extra_context"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
|
|
|
|
# Abort all existing flows
|
|
|
|
for flow in hass.config_entries.flow.async_progress():
|
|
|
|
hass.config_entries.flow.async_abort(flow["flow_id"])
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check that we can't start reauth flows with active reconfigure flow
|
|
|
|
entry.async_start_reconfigure(hass, {"extra_context": "some_extra_context"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
entry.async_start_reauth(hass, {"extra_context": "some_extra_context"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(hass.config_entries.flow.async_progress()) == 1
|
|
|
|
|
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
async def test_get_active_flows(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
2022-11-09 22:36:50 +00:00
|
|
|
"""Test the async_get_active_flows helper."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
2024-05-11 23:20:08 +00:00
|
|
|
entry.add_to_hass(hass)
|
2022-11-09 22:36:50 +00:00
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2022-11-09 22:36:50 +00:00
|
|
|
|
2024-05-11 23:20:08 +00:00
|
|
|
await manager.async_setup(entry.entry_id)
|
2022-11-09 22:36:50 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
flow = hass.config_entries.flow
|
|
|
|
with patch.object(flow, "async_init", wraps=flow.async_init):
|
|
|
|
entry.async_start_reauth(
|
|
|
|
hass,
|
|
|
|
context={"extra_context": "some_extra_context"},
|
|
|
|
data={"extra_data": 1234},
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# Check that there's an active reauth flow:
|
|
|
|
active_reauth_flow = next(
|
|
|
|
iter(entry.async_get_active_flows(hass, {config_entries.SOURCE_REAUTH})), None
|
|
|
|
)
|
|
|
|
assert active_reauth_flow is not None
|
|
|
|
|
|
|
|
# Check that there isn't any other flow (in this case, a user flow):
|
|
|
|
active_user_flow = next(
|
|
|
|
iter(entry.async_get_active_flows(hass, {config_entries.SOURCE_USER})), None
|
|
|
|
)
|
|
|
|
assert active_user_flow is None
|
2022-11-17 20:52:57 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_async_wait_component_dynamic(hass: HomeAssistant) -> None:
|
2022-11-17 20:52:57 +00:00
|
|
|
"""Test async_wait_component for a config entry which is dynamically loaded."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
mock_integration(hass, MockModule("test", async_setup_entry=mock_setup_entry))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2022-11-17 20:52:57 +00:00
|
|
|
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
# The config entry is not loaded, and is also not scheduled to load
|
|
|
|
assert await hass.config_entries.async_wait_component(entry) is False
|
|
|
|
|
|
|
|
await hass.config_entries.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
# The config entry is loaded
|
|
|
|
assert await hass.config_entries.async_wait_component(entry) is True
|
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_async_wait_component_startup(hass: HomeAssistant) -> None:
|
2022-11-17 20:52:57 +00:00
|
|
|
"""Test async_wait_component for a config entry which is loaded at startup."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
|
|
|
|
|
|
|
setup_stall = asyncio.Event()
|
|
|
|
setup_started = asyncio.Event()
|
|
|
|
|
|
|
|
async def mock_setup(hass: HomeAssistant, _) -> bool:
|
|
|
|
setup_started.set()
|
|
|
|
await setup_stall.wait()
|
|
|
|
return True
|
|
|
|
|
|
|
|
mock_setup_entry = AsyncMock(return_value=True)
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule("test", async_setup=mock_setup, async_setup_entry=mock_setup_entry),
|
|
|
|
)
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2022-11-17 20:52:57 +00:00
|
|
|
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
# The config entry is not loaded, and is also not scheduled to load
|
|
|
|
assert await hass.config_entries.async_wait_component(entry) is False
|
|
|
|
|
|
|
|
# Mark the component as scheduled to be loaded
|
|
|
|
async_set_domains_to_be_loaded(hass, {"test"})
|
|
|
|
|
|
|
|
# Start loading the component, including its config entries
|
|
|
|
hass.async_create_task(async_setup_component(hass, "test", {}))
|
|
|
|
await setup_started.wait()
|
|
|
|
|
|
|
|
# The component is not yet loaded
|
|
|
|
assert "test" not in hass.config.components
|
|
|
|
|
|
|
|
# Allow setup to proceed
|
|
|
|
setup_stall.set()
|
|
|
|
|
|
|
|
# The component is scheduled to load, this will block until the config entry is loaded
|
|
|
|
assert await hass.config_entries.async_wait_component(entry) is True
|
|
|
|
|
|
|
|
# The component has been loaded
|
|
|
|
assert "test" in hass.config.components
|
2022-11-24 11:18:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_options_flow_options_not_mutated() -> None:
|
|
|
|
"""Test that OptionsFlowWithConfigEntry doesn't mutate entry options."""
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="test",
|
|
|
|
data={"first": True},
|
|
|
|
options={"sub_dict": {"1": "one"}, "sub_list": ["one"]},
|
|
|
|
)
|
|
|
|
|
|
|
|
options_flow = config_entries.OptionsFlowWithConfigEntry(entry)
|
|
|
|
|
|
|
|
options_flow._options["sub_dict"]["2"] = "two"
|
|
|
|
options_flow._options["sub_list"].append("two")
|
|
|
|
|
|
|
|
assert options_flow._options == {
|
|
|
|
"sub_dict": {"1": "one", "2": "two"},
|
|
|
|
"sub_list": ["one", "two"],
|
|
|
|
}
|
|
|
|
assert entry.options == {"sub_dict": {"1": "one"}, "sub_list": ["one"]}
|
2023-01-17 14:26:17 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_initializing_flows_canceled_on_shutdown(
|
2023-03-16 10:08:47 +00:00
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
2023-02-21 08:27:13 +00:00
|
|
|
) -> None:
|
2023-01-17 14:26:17 +00:00
|
|
|
"""Test that initializing flows are canceled on shutdown."""
|
|
|
|
|
|
|
|
class MockFlowHandler(config_entries.ConfigFlow):
|
|
|
|
"""Define a mock flow handler."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_reauth(self, data):
|
|
|
|
"""Mock Reauth."""
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
|
2023-02-14 04:16:59 +00:00
|
|
|
mock_integration(hass, MockModule("test"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2023-02-14 04:16:59 +00:00
|
|
|
|
2023-01-17 14:26:17 +00:00
|
|
|
with patch.dict(
|
|
|
|
config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler}
|
|
|
|
):
|
|
|
|
task = asyncio.create_task(
|
|
|
|
manager.flow.async_init("test", context={"source": "reauth"})
|
|
|
|
)
|
2023-02-14 04:16:59 +00:00
|
|
|
await hass.async_block_till_done()
|
2024-02-24 07:37:33 +00:00
|
|
|
manager.flow.async_shutdown()
|
2023-01-17 14:26:17 +00:00
|
|
|
|
2023-02-14 04:16:59 +00:00
|
|
|
with pytest.raises(asyncio.exceptions.CancelledError):
|
|
|
|
await task
|
2023-02-17 18:50:05 +00:00
|
|
|
|
|
|
|
|
2023-02-21 08:27:13 +00:00
|
|
|
async def test_task_tracking(hass: HomeAssistant) -> None:
|
2023-02-17 18:50:05 +00:00
|
|
|
"""Test task tracking for a config entry."""
|
|
|
|
entry = MockConfigEntry(title="test_title", domain="test")
|
|
|
|
|
|
|
|
event = asyncio.Event()
|
|
|
|
results = []
|
|
|
|
|
2023-04-17 12:41:25 +00:00
|
|
|
async def test_task() -> None:
|
2023-02-17 18:50:05 +00:00
|
|
|
try:
|
|
|
|
await event.wait()
|
|
|
|
results.append("normal")
|
|
|
|
except asyncio.CancelledError:
|
|
|
|
results.append("background")
|
|
|
|
raise
|
|
|
|
|
2023-04-17 12:41:25 +00:00
|
|
|
async def test_unload() -> None:
|
|
|
|
await event.wait()
|
|
|
|
results.append("on_unload")
|
|
|
|
|
|
|
|
entry.async_on_unload(test_unload)
|
2023-02-17 18:50:05 +00:00
|
|
|
entry.async_create_task(hass, test_task())
|
2024-02-26 16:36:46 +00:00
|
|
|
entry.async_create_background_task(
|
|
|
|
hass, test_task(), "background-task-name", eager_start=True
|
|
|
|
)
|
|
|
|
entry.async_create_background_task(
|
|
|
|
hass, test_task(), "background-task-name", eager_start=False
|
|
|
|
)
|
2023-02-17 18:50:05 +00:00
|
|
|
await asyncio.sleep(0)
|
|
|
|
hass.loop.call_soon(event.set)
|
2023-04-17 12:41:25 +00:00
|
|
|
await entry._async_process_on_unload(hass)
|
2024-03-08 04:32:26 +00:00
|
|
|
assert results == [
|
|
|
|
"background",
|
|
|
|
"background",
|
|
|
|
"normal",
|
2024-03-17 00:43:49 +00:00
|
|
|
"on_unload",
|
2024-03-08 04:32:26 +00:00
|
|
|
]
|
2023-08-22 08:29:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_preview_supported(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
|
|
|
"""Test preview support."""
|
|
|
|
|
|
|
|
preview_calls = []
|
|
|
|
|
|
|
|
class MockFlowHandler(config_entries.ConfigFlow):
|
|
|
|
"""Define a mock flow handler."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_test1(self, data):
|
|
|
|
"""Mock Reauth."""
|
|
|
|
return self.async_show_form(step_id="next")
|
|
|
|
|
|
|
|
async def async_step_test2(self, data):
|
|
|
|
"""Mock Reauth."""
|
|
|
|
return self.async_show_form(step_id="next", preview="test")
|
|
|
|
|
2023-10-19 11:34:10 +00:00
|
|
|
async def async_step_next(self, user_input=None):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2023-08-22 08:29:16 +00:00
|
|
|
@staticmethod
|
2023-08-24 09:59:24 +00:00
|
|
|
async def async_setup_preview(hass: HomeAssistant) -> None:
|
2023-08-22 08:29:16 +00:00
|
|
|
"""Set up preview."""
|
|
|
|
preview_calls.append(None)
|
|
|
|
|
|
|
|
mock_integration(hass, MockModule("test"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2023-08-22 08:29:16 +00:00
|
|
|
|
|
|
|
assert len(preview_calls) == 0
|
|
|
|
|
|
|
|
with patch.dict(
|
|
|
|
config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler}
|
|
|
|
):
|
|
|
|
result = await manager.flow.async_init("test", context={"source": "test1"})
|
|
|
|
|
|
|
|
assert len(preview_calls) == 0
|
|
|
|
assert result["preview"] is None
|
|
|
|
|
|
|
|
result = await manager.flow.async_init("test", context={"source": "test2"})
|
|
|
|
|
|
|
|
assert len(preview_calls) == 1
|
|
|
|
assert result["preview"] == "test"
|
|
|
|
|
|
|
|
|
|
|
|
async def test_preview_not_supported(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
|
|
|
"""Test preview support."""
|
|
|
|
|
|
|
|
class MockFlowHandler(config_entries.ConfigFlow):
|
|
|
|
"""Define a mock flow handler."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
2024-06-12 10:25:29 +00:00
|
|
|
async def async_step_user(self, user_input):
|
2023-08-22 08:29:16 +00:00
|
|
|
"""Mock Reauth."""
|
|
|
|
return self.async_show_form(step_id="user_confirm")
|
|
|
|
|
2023-10-19 11:34:10 +00:00
|
|
|
async def async_step_user_confirm(self, user_input=None):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2023-08-22 08:29:16 +00:00
|
|
|
mock_integration(hass, MockModule("test"))
|
2023-11-16 15:55:08 +00:00
|
|
|
mock_platform(hass, "test.config_flow", None)
|
2023-08-22 08:29:16 +00:00
|
|
|
|
|
|
|
with patch.dict(
|
|
|
|
config_entries.HANDLERS, {"comp": MockFlowHandler, "test": MockFlowHandler}
|
|
|
|
):
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"test", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
|
|
|
|
assert result["preview"] is None
|
2024-01-13 20:34:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_raise_trying_to_add_same_config_entry_twice(
|
|
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
|
|
|
) -> None:
|
|
|
|
"""Test we log an error if trying to add same config entry twice."""
|
|
|
|
entry = MockConfigEntry(domain="test")
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
assert f"An entry with the id {entry.entry_id} already exists" in caplog.text
|
2024-01-22 16:40:20 +00:00
|
|
|
|
|
|
|
|
2024-04-24 13:13:33 +00:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
(
|
|
|
|
"title",
|
|
|
|
"unique_id",
|
|
|
|
"data_vendor",
|
|
|
|
"options_vendor",
|
|
|
|
"kwargs",
|
|
|
|
"calls_entry_load_unload",
|
|
|
|
),
|
|
|
|
[
|
|
|
|
(
|
|
|
|
("Test", "Updated title"),
|
|
|
|
("1234", "5678"),
|
|
|
|
("data", "data2"),
|
|
|
|
("options", "options2"),
|
|
|
|
{},
|
|
|
|
(2, 1),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
("Test", "Test"),
|
|
|
|
("1234", "1234"),
|
|
|
|
("data", "data"),
|
|
|
|
("options", "options"),
|
|
|
|
{},
|
|
|
|
(2, 1),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
("Test", "Updated title"),
|
|
|
|
("1234", "5678"),
|
|
|
|
("data", "data2"),
|
|
|
|
("options", "options2"),
|
|
|
|
{"reload_even_if_entry_is_unchanged": True},
|
|
|
|
(2, 1),
|
|
|
|
),
|
|
|
|
(
|
|
|
|
("Test", "Test"),
|
|
|
|
("1234", "1234"),
|
|
|
|
("data", "data"),
|
|
|
|
("options", "options"),
|
|
|
|
{"reload_even_if_entry_is_unchanged": False},
|
|
|
|
(1, 0),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
ids=[
|
|
|
|
"changed_entry_default",
|
|
|
|
"unchanged_entry_default",
|
|
|
|
"changed_entry_explicit_reload",
|
|
|
|
"changed_entry_no_reload",
|
|
|
|
],
|
|
|
|
)
|
2024-01-22 16:40:20 +00:00
|
|
|
async def test_update_entry_and_reload(
|
2024-04-24 13:13:33 +00:00
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
title: tuple[str, str],
|
|
|
|
unique_id: tuple[str, str],
|
|
|
|
data_vendor: tuple[str, str],
|
|
|
|
options_vendor: tuple[str, str],
|
|
|
|
kwargs: dict[str, Any],
|
|
|
|
calls_entry_load_unload: tuple[int, int],
|
2024-01-22 16:40:20 +00:00
|
|
|
) -> None:
|
|
|
|
"""Test updating an entry and reloading."""
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
2024-04-24 13:13:33 +00:00
|
|
|
unique_id=unique_id[0],
|
|
|
|
title=title[0],
|
|
|
|
data={"vendor": data_vendor[0]},
|
|
|
|
options={"vendor": options_vendor[0]},
|
2024-01-22 16:40:20 +00:00
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
2024-04-24 13:13:33 +00:00
|
|
|
comp = MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=AsyncMock(return_value=True),
|
|
|
|
async_unload_entry=AsyncMock(return_value=True),
|
2024-01-22 16:40:20 +00:00
|
|
|
)
|
2024-04-24 13:13:33 +00:00
|
|
|
mock_integration(hass, comp)
|
2024-01-22 16:40:20 +00:00
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
|
2024-04-24 13:13:33 +00:00
|
|
|
await hass.config_entries.async_setup(entry.entry_id)
|
|
|
|
|
2024-01-22 16:40:20 +00:00
|
|
|
class MockFlowHandler(config_entries.ConfigFlow):
|
|
|
|
"""Define a mock flow handler."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_reauth(self, data):
|
|
|
|
"""Mock Reauth."""
|
|
|
|
return self.async_update_reload_and_abort(
|
|
|
|
entry=entry,
|
2024-04-24 13:13:33 +00:00
|
|
|
unique_id=unique_id[1],
|
|
|
|
title=title[1],
|
|
|
|
data={"vendor": data_vendor[1]},
|
|
|
|
options={"vendor": options_vendor[1]},
|
|
|
|
**kwargs,
|
2024-01-22 16:40:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
with patch.dict(config_entries.HANDLERS, {"comp": MockFlowHandler}):
|
|
|
|
task = await manager.flow.async_init("comp", context={"source": "reauth"})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2024-04-24 13:13:33 +00:00
|
|
|
assert entry.title == title[1]
|
|
|
|
assert entry.unique_id == unique_id[1]
|
|
|
|
assert entry.data == {"vendor": data_vendor[1]}
|
|
|
|
assert entry.options == {"vendor": options_vendor[1]}
|
2024-01-22 16:40:20 +00:00
|
|
|
assert entry.state == config_entries.ConfigEntryState.LOADED
|
|
|
|
assert task["type"] == FlowResultType.ABORT
|
|
|
|
assert task["reason"] == "reauth_successful"
|
2024-04-24 13:13:33 +00:00
|
|
|
# Assert entry was reloaded
|
|
|
|
assert len(comp.async_setup_entry.mock_calls) == calls_entry_load_unload[0]
|
|
|
|
assert len(comp.async_unload_entry.mock_calls) == calls_entry_load_unload[1]
|
2024-02-08 14:39:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("unique_id", [["blah", "bleh"], {"key": "value"}])
|
|
|
|
async def test_unhashable_unique_id(
|
|
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, unique_id: Any
|
|
|
|
) -> None:
|
|
|
|
"""Test the ConfigEntryItems user dict handles unhashable unique_id."""
|
|
|
|
entries = config_entries.ConfigEntryItems(hass)
|
|
|
|
entry = config_entries.ConfigEntry(
|
2024-05-11 19:45:03 +00:00
|
|
|
data={},
|
2024-02-08 14:39:01 +00:00
|
|
|
domain="test",
|
|
|
|
entry_id="mock_id",
|
2024-05-11 19:45:03 +00:00
|
|
|
minor_version=1,
|
|
|
|
options={},
|
2024-02-08 14:39:01 +00:00
|
|
|
source="test",
|
2024-05-11 19:45:03 +00:00
|
|
|
title="title",
|
2024-02-08 14:39:01 +00:00
|
|
|
unique_id=unique_id,
|
2024-05-11 19:45:03 +00:00
|
|
|
version=1,
|
2024-02-08 14:39:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
entries[entry.entry_id] = entry
|
|
|
|
assert (
|
|
|
|
"Config entry 'title' from integration test has an invalid unique_id "
|
2024-05-08 21:54:49 +00:00
|
|
|
f"'{unique_id!s}'"
|
2024-02-08 14:39:01 +00:00
|
|
|
) in caplog.text
|
|
|
|
|
|
|
|
assert entry.entry_id in entries
|
|
|
|
assert entries[entry.entry_id] is entry
|
|
|
|
assert entries.get_entry_by_domain_and_unique_id("test", unique_id) == entry
|
|
|
|
del entries[entry.entry_id]
|
|
|
|
assert not entries
|
|
|
|
assert entries.get_entry_by_domain_and_unique_id("test", unique_id) is None
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("unique_id", [123])
|
|
|
|
async def test_hashable_non_string_unique_id(
|
|
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, unique_id: Any
|
|
|
|
) -> None:
|
|
|
|
"""Test the ConfigEntryItems user dict handles hashable non string unique_id."""
|
|
|
|
entries = config_entries.ConfigEntryItems(hass)
|
|
|
|
entry = config_entries.ConfigEntry(
|
2024-05-11 19:45:03 +00:00
|
|
|
data={},
|
2024-02-08 14:39:01 +00:00
|
|
|
domain="test",
|
|
|
|
entry_id="mock_id",
|
2024-05-11 19:45:03 +00:00
|
|
|
minor_version=1,
|
|
|
|
options={},
|
2024-02-08 14:39:01 +00:00
|
|
|
source="test",
|
2024-05-11 19:45:03 +00:00
|
|
|
title="title",
|
2024-02-08 14:39:01 +00:00
|
|
|
unique_id=unique_id,
|
2024-05-11 19:45:03 +00:00
|
|
|
version=1,
|
2024-02-08 14:39:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
entries[entry.entry_id] = entry
|
|
|
|
assert (
|
|
|
|
"Config entry 'title' from integration test has an invalid unique_id"
|
|
|
|
) not in caplog.text
|
|
|
|
|
|
|
|
assert entry.entry_id in entries
|
|
|
|
assert entries[entry.entry_id] is entry
|
|
|
|
assert entries.get_entry_by_domain_and_unique_id("test", unique_id) == entry
|
|
|
|
del entries[entry.entry_id]
|
|
|
|
assert not entries
|
|
|
|
assert entries.get_entry_by_domain_and_unique_id("test", unique_id) is None
|
2024-02-16 16:15:05 +00:00
|
|
|
|
|
|
|
|
2024-02-27 14:47:56 +00:00
|
|
|
@pytest.mark.parametrize(
|
2024-02-27 17:28:19 +00:00
|
|
|
("source", "user_input", "expected_result"),
|
2024-02-27 14:47:56 +00:00
|
|
|
[
|
|
|
|
(
|
|
|
|
config_entries.SOURCE_IGNORE,
|
|
|
|
{"unique_id": "blah", "title": "blah"},
|
|
|
|
{"type": data_entry_flow.FlowResultType.CREATE_ENTRY},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
config_entries.SOURCE_REAUTH,
|
|
|
|
None,
|
|
|
|
{"type": data_entry_flow.FlowResultType.FORM, "step_id": "reauth_confirm"},
|
|
|
|
),
|
2024-05-23 06:41:12 +00:00
|
|
|
(
|
|
|
|
config_entries.SOURCE_RECONFIGURE,
|
|
|
|
None,
|
|
|
|
{"type": data_entry_flow.FlowResultType.FORM, "step_id": "reauth_confirm"},
|
|
|
|
),
|
2024-02-27 14:47:56 +00:00
|
|
|
(
|
|
|
|
config_entries.SOURCE_UNIGNORE,
|
|
|
|
None,
|
|
|
|
{"type": data_entry_flow.FlowResultType.ABORT, "reason": "not_implemented"},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
config_entries.SOURCE_USER,
|
|
|
|
None,
|
2024-02-27 17:28:19 +00:00
|
|
|
{
|
|
|
|
"type": data_entry_flow.FlowResultType.ABORT,
|
|
|
|
"reason": "single_instance_allowed",
|
|
|
|
"translation_domain": HA_DOMAIN,
|
|
|
|
},
|
2024-02-27 14:47:56 +00:00
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_starting_config_flow_on_single_config_entry(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
source: str,
|
|
|
|
user_input: dict,
|
|
|
|
expected_result: dict,
|
2024-02-26 18:00:33 +00:00
|
|
|
) -> None:
|
2024-02-27 14:47:56 +00:00
|
|
|
"""Test starting a config flow for a single config entry integration.
|
|
|
|
|
|
|
|
In this test, the integration has one ignored flow and one entry added by user.
|
|
|
|
"""
|
2024-02-26 18:00:33 +00:00
|
|
|
integration = loader.Integration(
|
|
|
|
hass,
|
|
|
|
"components.comp",
|
|
|
|
None,
|
|
|
|
{
|
|
|
|
"name": "Comp",
|
|
|
|
"dependencies": [],
|
|
|
|
"requirements": [],
|
|
|
|
"domain": "comp",
|
|
|
|
"single_config_entry": True,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
unique_id="1234",
|
|
|
|
title="Test",
|
|
|
|
data={"vendor": "data"},
|
|
|
|
options={"vendor": "options"},
|
|
|
|
)
|
|
|
|
entry.add_to_hass(hass)
|
2024-02-27 14:47:56 +00:00
|
|
|
ignored_entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
unique_id="2345",
|
|
|
|
title="Test",
|
|
|
|
data={"vendor": "data"},
|
|
|
|
options={"vendor": "options"},
|
|
|
|
source=config_entries.SOURCE_IGNORE,
|
|
|
|
)
|
|
|
|
ignored_entry.add_to_hass(hass)
|
2024-02-26 18:00:33 +00:00
|
|
|
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
"homeassistant.loader.async_get_integration",
|
|
|
|
return_value=integration,
|
2024-02-27 17:28:19 +00:00
|
|
|
):
|
2024-02-27 14:47:56 +00:00
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
|
|
"comp", context={"source": source}, data=user_input
|
|
|
|
)
|
|
|
|
|
|
|
|
for key in expected_result:
|
|
|
|
assert result[key] == expected_result[key]
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
2024-02-27 17:28:19 +00:00
|
|
|
("source", "user_input", "expected_result"),
|
2024-02-27 14:47:56 +00:00
|
|
|
[
|
|
|
|
(
|
|
|
|
config_entries.SOURCE_IGNORE,
|
|
|
|
{"unique_id": "blah", "title": "blah"},
|
|
|
|
{"type": data_entry_flow.FlowResultType.CREATE_ENTRY},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
config_entries.SOURCE_REAUTH,
|
|
|
|
None,
|
|
|
|
{"type": data_entry_flow.FlowResultType.FORM, "step_id": "reauth_confirm"},
|
|
|
|
),
|
2024-05-23 06:41:12 +00:00
|
|
|
(
|
|
|
|
config_entries.SOURCE_RECONFIGURE,
|
|
|
|
None,
|
|
|
|
{"type": data_entry_flow.FlowResultType.FORM, "step_id": "reauth_confirm"},
|
|
|
|
),
|
2024-02-27 14:47:56 +00:00
|
|
|
(
|
|
|
|
config_entries.SOURCE_UNIGNORE,
|
|
|
|
None,
|
|
|
|
{"type": data_entry_flow.FlowResultType.ABORT, "reason": "not_implemented"},
|
|
|
|
),
|
|
|
|
(
|
|
|
|
config_entries.SOURCE_USER,
|
|
|
|
None,
|
|
|
|
{"type": data_entry_flow.FlowResultType.ABORT, "reason": "not_implemented"},
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_starting_config_flow_on_single_config_entry_2(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
source: str,
|
|
|
|
user_input: dict,
|
|
|
|
expected_result: dict,
|
|
|
|
) -> None:
|
|
|
|
"""Test starting a config flow for a single config entry integration.
|
|
|
|
|
|
|
|
In this test, the integration has one ignored flow but no entry added by user.
|
|
|
|
"""
|
|
|
|
integration = loader.Integration(
|
|
|
|
hass,
|
|
|
|
"components.comp",
|
|
|
|
None,
|
|
|
|
{
|
|
|
|
"name": "Comp",
|
|
|
|
"dependencies": [],
|
|
|
|
"requirements": [],
|
|
|
|
"domain": "comp",
|
|
|
|
"single_config_entry": True,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
ignored_entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
unique_id="2345",
|
|
|
|
title="Test",
|
|
|
|
data={"vendor": "data"},
|
|
|
|
options={"vendor": "options"},
|
|
|
|
source=config_entries.SOURCE_IGNORE,
|
|
|
|
)
|
|
|
|
ignored_entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
"homeassistant.loader.async_get_integration",
|
|
|
|
return_value=integration,
|
2024-02-27 17:28:19 +00:00
|
|
|
):
|
2024-02-27 14:47:56 +00:00
|
|
|
result = await hass.config_entries.flow.async_init(
|
|
|
|
"comp", context={"source": source}, data=user_input
|
|
|
|
)
|
|
|
|
|
|
|
|
for key in expected_result:
|
|
|
|
assert result[key] == expected_result[key]
|
2024-02-26 18:00:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_avoid_adding_second_config_entry_on_single_config_entry(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
|
|
|
"""Test that we cannot add a second entry for a single config entry integration."""
|
2024-02-27 17:28:19 +00:00
|
|
|
|
|
|
|
class TestFlow(config_entries.ConfigFlow):
|
|
|
|
"""Test flow."""
|
|
|
|
|
|
|
|
VERSION = 1
|
|
|
|
|
|
|
|
async def async_step_user(self, user_input=None):
|
|
|
|
"""Test user step."""
|
|
|
|
if user_input is None:
|
|
|
|
return self.async_show_form(step_id="user")
|
|
|
|
|
|
|
|
return self.async_create_entry(title="yo", data={})
|
|
|
|
|
2024-02-26 18:00:33 +00:00
|
|
|
integration = loader.Integration(
|
|
|
|
hass,
|
|
|
|
"components.comp",
|
|
|
|
None,
|
|
|
|
{
|
|
|
|
"name": "Comp",
|
|
|
|
"dependencies": [],
|
|
|
|
"requirements": [],
|
|
|
|
"domain": "comp",
|
|
|
|
"single_config_entry": True,
|
|
|
|
},
|
|
|
|
)
|
2024-02-27 17:28:19 +00:00
|
|
|
mock_integration(hass, MockModule("comp"))
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
2024-02-26 18:00:33 +00:00
|
|
|
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
patch(
|
|
|
|
"homeassistant.loader.async_get_integration",
|
|
|
|
return_value=integration,
|
|
|
|
),
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
):
|
2024-02-27 17:28:19 +00:00
|
|
|
# Start a flow
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
|
|
|
|
|
|
|
# Add a config entry
|
|
|
|
entry = MockConfigEntry(
|
|
|
|
domain="comp",
|
|
|
|
unique_id="1234",
|
|
|
|
title="Test",
|
|
|
|
data={"vendor": "data"},
|
|
|
|
options={"vendor": "options"},
|
2024-02-26 18:00:33 +00:00
|
|
|
)
|
2024-02-27 17:28:19 +00:00
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
# Finish the in progress flow
|
|
|
|
result = await manager.flow.async_configure(
|
|
|
|
result["flow_id"], user_input={"host": "127.0.0.1"}
|
|
|
|
)
|
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.ABORT
|
|
|
|
assert result["reason"] == "single_instance_allowed"
|
|
|
|
assert result["translation_domain"] == HA_DOMAIN
|
2024-02-26 18:00:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_in_progress_get_canceled_when_entry_is_created(
|
|
|
|
hass: HomeAssistant, manager: config_entries.ConfigEntries
|
|
|
|
) -> None:
|
|
|
|
"""Test that we abort all in progress flows when a new entry is created on a single instance only integration."""
|
|
|
|
integration = loader.Integration(
|
|
|
|
hass,
|
|
|
|
"components.comp",
|
|
|
|
None,
|
|
|
|
{
|
|
|
|
"name": "Comp",
|
|
|
|
"dependencies": [],
|
|
|
|
"requirements": [],
|
|
|
|
"domain": "comp",
|
|
|
|
"single_config_entry": True,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
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."""
|
|
|
|
if user_input is not None:
|
|
|
|
return self.async_create_entry(title="Test Title", data=user_input)
|
|
|
|
|
|
|
|
return self.async_show_form(step_id="user")
|
|
|
|
|
2024-03-25 23:02:16 +00:00
|
|
|
with (
|
|
|
|
patch.dict(config_entries.HANDLERS, {"comp": TestFlow}),
|
|
|
|
patch(
|
|
|
|
"homeassistant.loader.async_get_integration",
|
|
|
|
return_value=integration,
|
|
|
|
),
|
2024-02-26 18:00:33 +00:00
|
|
|
):
|
|
|
|
# Create one to be in progress
|
|
|
|
result = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.FORM
|
|
|
|
|
|
|
|
# Will be canceled
|
|
|
|
result2 = await manager.flow.async_init(
|
|
|
|
"comp", context={"source": config_entries.SOURCE_USER}
|
|
|
|
)
|
|
|
|
assert result2["type"] == data_entry_flow.FlowResultType.FORM
|
|
|
|
|
|
|
|
result = await manager.flow.async_configure(
|
|
|
|
result["flow_id"], user_input={"host": "127.0.0.1"}
|
|
|
|
)
|
|
|
|
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
|
|
|
|
|
|
|
|
assert len(manager.flow.async_progress()) == 0
|
|
|
|
assert len(manager.async_entries()) == 1
|
|
|
|
|
|
|
|
|
2024-02-16 16:15:05 +00:00
|
|
|
async def test_directly_mutating_blocked(
|
|
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
|
|
|
) -> None:
|
|
|
|
"""Test directly mutating a ConfigEntry is blocked."""
|
|
|
|
entry = MockConfigEntry(domain="test")
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
with pytest.raises(AttributeError, match="entry_id cannot be changed"):
|
|
|
|
entry.entry_id = "new_entry_id"
|
|
|
|
|
|
|
|
with pytest.raises(AttributeError, match="domain cannot be changed"):
|
|
|
|
entry.domain = "new_domain"
|
|
|
|
|
|
|
|
with pytest.raises(AttributeError, match="state cannot be changed"):
|
|
|
|
entry.state = config_entries.ConfigEntryState.FAILED_UNLOAD
|
|
|
|
|
|
|
|
with pytest.raises(AttributeError, match="reason cannot be changed"):
|
|
|
|
entry.reason = "new_reason"
|
|
|
|
|
|
|
|
with pytest.raises(
|
|
|
|
AttributeError,
|
|
|
|
match="unique_id cannot be changed directly, use async_update_entry instead",
|
|
|
|
):
|
|
|
|
entry.unique_id = "new_id"
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"field",
|
2024-03-19 08:01:07 +00:00
|
|
|
[
|
2024-02-16 16:15:05 +00:00
|
|
|
"data",
|
|
|
|
"options",
|
|
|
|
"title",
|
|
|
|
"pref_disable_new_entities",
|
|
|
|
"pref_disable_polling",
|
|
|
|
"minor_version",
|
|
|
|
"version",
|
2024-03-19 08:01:07 +00:00
|
|
|
],
|
2024-02-16 16:15:05 +00:00
|
|
|
)
|
|
|
|
async def test_report_direct_mutation_of_config_entry(
|
|
|
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, field: str
|
|
|
|
) -> None:
|
|
|
|
"""Test directly mutating a ConfigEntry is reported."""
|
|
|
|
entry = MockConfigEntry(domain="test")
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
setattr(entry, field, "new_value")
|
|
|
|
|
|
|
|
assert (
|
|
|
|
f'Detected code that sets "{field}" directly to update a config entry. '
|
|
|
|
"This is deprecated and will stop working in Home Assistant 2024.9, "
|
|
|
|
"it should be updated to use async_update_entry instead. Please report this issue."
|
|
|
|
) in caplog.text
|
|
|
|
|
|
|
|
|
|
|
|
async def test_updating_non_added_entry_raises(hass: HomeAssistant) -> None:
|
|
|
|
"""Test updating a non added entry raises UnknownEntry."""
|
|
|
|
entry = MockConfigEntry(domain="test")
|
|
|
|
|
|
|
|
with pytest.raises(config_entries.UnknownEntry, match=entry.entry_id):
|
|
|
|
hass.config_entries.async_update_entry(entry, unique_id="new_id")
|
2024-04-30 23:47:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_reload_during_setup(hass: HomeAssistant) -> None:
|
|
|
|
"""Test reload during setup waits."""
|
|
|
|
entry = MockConfigEntry(domain="comp", data={"value": "initial"})
|
|
|
|
entry.add_to_hass(hass)
|
|
|
|
|
|
|
|
setup_start_future = hass.loop.create_future()
|
|
|
|
setup_finish_future = hass.loop.create_future()
|
|
|
|
in_setup = False
|
|
|
|
setup_calls = 0
|
|
|
|
|
|
|
|
async def mock_async_setup_entry(hass, entry):
|
|
|
|
"""Mock setting up an entry."""
|
|
|
|
nonlocal in_setup
|
|
|
|
nonlocal setup_calls
|
|
|
|
setup_calls += 1
|
|
|
|
assert not in_setup
|
|
|
|
in_setup = True
|
|
|
|
setup_start_future.set_result(None)
|
|
|
|
await setup_finish_future
|
|
|
|
in_setup = False
|
|
|
|
return True
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"comp",
|
|
|
|
async_setup_entry=mock_async_setup_entry,
|
|
|
|
async_unload_entry=AsyncMock(return_value=True),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(hass, "comp.config_flow", None)
|
|
|
|
|
|
|
|
setup_task = hass.async_create_task(async_setup_component(hass, "comp", {}))
|
|
|
|
|
|
|
|
await setup_start_future # ensure we are in the setup
|
|
|
|
reload_task = hass.async_create_task(
|
|
|
|
hass.config_entries.async_reload(entry.entry_id)
|
|
|
|
)
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
setup_finish_future.set_result(None)
|
|
|
|
await setup_task
|
|
|
|
await reload_task
|
|
|
|
assert setup_calls == 2
|
2024-05-21 00:01:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"exc",
|
|
|
|
[
|
|
|
|
ConfigEntryError,
|
|
|
|
ConfigEntryAuthFailed,
|
|
|
|
ConfigEntryNotReady,
|
|
|
|
],
|
|
|
|
)
|
|
|
|
async def test_raise_wrong_exception_in_forwarded_platform(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
exc: Exception,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
) -> None:
|
|
|
|
"""Test that we can remove an entry."""
|
|
|
|
|
|
|
|
async def mock_setup_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock setting up entry."""
|
|
|
|
await hass.config_entries.async_forward_entry_setups(entry, ["light"])
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def mock_unload_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock unloading an entry."""
|
|
|
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
|
|
|
assert result
|
|
|
|
return result
|
|
|
|
|
|
|
|
mock_remove_entry = AsyncMock(return_value=None)
|
|
|
|
|
|
|
|
async def mock_setup_entry_platform(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entry: config_entries.ConfigEntry,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
) -> None:
|
|
|
|
"""Mock setting up platform."""
|
|
|
|
raise exc
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"test",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=mock_unload_entry,
|
|
|
|
async_remove_entry=mock_remove_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(
|
|
|
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
|
|
|
)
|
|
|
|
mock_platform(hass, "test.config_flow", None)
|
|
|
|
|
|
|
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
|
|
|
entry.add_to_manager(manager)
|
|
|
|
|
|
|
|
# Setup entry
|
|
|
|
await manager.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
exc_type_name = type(exc()).__name__
|
|
|
|
assert (
|
|
|
|
f"test raises exception {exc_type_name} in forwarded platform light;"
|
|
|
|
in caplog.text
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
f"Instead raise {exc_type_name} before calling async_forward_entry_setups"
|
|
|
|
in caplog.text
|
|
|
|
)
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
|
|
|
|
|
2024-06-13 01:06:11 +00:00
|
|
|
async def test_config_entry_unloaded_during_platform_setups(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
) -> None:
|
|
|
|
"""Test async_forward_entry_setups not being awaited."""
|
|
|
|
task = None
|
|
|
|
|
|
|
|
async def mock_setup_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock setting up entry."""
|
|
|
|
|
|
|
|
# Call async_forward_entry_setups in a non-tracked task
|
|
|
|
# so we can unload the config entry during the setup
|
|
|
|
def _late_setup():
|
|
|
|
nonlocal task
|
|
|
|
task = asyncio.create_task(
|
|
|
|
hass.config_entries.async_forward_entry_setups(entry, ["light"])
|
|
|
|
)
|
|
|
|
|
|
|
|
hass.loop.call_soon(_late_setup)
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def mock_unload_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock unloading an entry."""
|
|
|
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
|
|
|
assert result
|
|
|
|
return result
|
|
|
|
|
|
|
|
mock_remove_entry = AsyncMock(return_value=None)
|
|
|
|
|
|
|
|
async def mock_setup_entry_platform(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entry: config_entries.ConfigEntry,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
) -> None:
|
|
|
|
"""Mock setting up platform."""
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"test",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=mock_unload_entry,
|
|
|
|
async_remove_entry=mock_remove_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(
|
|
|
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
|
|
|
)
|
|
|
|
mock_platform(hass, "test.config_flow", None)
|
|
|
|
|
|
|
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
|
|
|
entry.add_to_manager(manager)
|
|
|
|
|
|
|
|
# Setup entry
|
|
|
|
await manager.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
await manager.async_unload(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
del task
|
|
|
|
|
|
|
|
assert (
|
2024-06-14 06:00:36 +00:00
|
|
|
"OperationNotAllowed: The config entry 'Mock Title' (test) with "
|
|
|
|
"entry_id 'test2' cannot forward setup for ['light'] because it is "
|
|
|
|
"in state ConfigEntryState.NOT_LOADED, but needs to be in the "
|
|
|
|
"ConfigEntryState.LOADED state"
|
2024-06-13 01:06:11 +00:00
|
|
|
) in caplog.text
|
|
|
|
|
|
|
|
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
async def test_non_awaited_async_forward_entry_setups(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
) -> None:
|
|
|
|
"""Test async_forward_entry_setups not being awaited."""
|
2024-06-13 01:06:11 +00:00
|
|
|
forward_event = asyncio.Event()
|
|
|
|
task: asyncio.Task | None = None
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
|
|
|
|
async def mock_setup_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock setting up entry."""
|
|
|
|
# Call async_forward_entry_setups without awaiting it
|
|
|
|
# This is not allowed and will raise a warning
|
2024-06-13 01:06:11 +00:00
|
|
|
nonlocal task
|
|
|
|
task = create_eager_task(
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
hass.config_entries.async_forward_entry_setups(entry, ["light"])
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def mock_unload_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock unloading an entry."""
|
|
|
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
|
|
|
assert result
|
|
|
|
return result
|
|
|
|
|
|
|
|
mock_remove_entry = AsyncMock(return_value=None)
|
|
|
|
|
|
|
|
async def mock_setup_entry_platform(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entry: config_entries.ConfigEntry,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
) -> None:
|
|
|
|
"""Mock setting up platform."""
|
2024-06-13 01:06:11 +00:00
|
|
|
await forward_event.wait()
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"test",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=mock_unload_entry,
|
|
|
|
async_remove_entry=mock_remove_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(
|
|
|
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
|
|
|
)
|
|
|
|
mock_platform(hass, "test.config_flow", None)
|
|
|
|
|
|
|
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
|
|
|
entry.add_to_manager(manager)
|
|
|
|
|
|
|
|
# Setup entry
|
|
|
|
await manager.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
2024-06-13 01:06:11 +00:00
|
|
|
forward_event.set()
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
await task
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
|
|
|
|
assert (
|
2024-06-13 01:06:11 +00:00
|
|
|
"Detected code that calls async_forward_entry_setups for integration "
|
|
|
|
"test with title: Mock Title and entry_id: test2, during setup without "
|
|
|
|
"awaiting async_forward_entry_setups, which can cause the setup lock "
|
|
|
|
"to be released before the setup is done. This will stop working in "
|
|
|
|
"Home Assistant 2025.1. Please report this issue."
|
|
|
|
) in caplog.text
|
|
|
|
|
|
|
|
|
|
|
|
async def test_non_awaited_async_forward_entry_setup(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
) -> None:
|
|
|
|
"""Test async_forward_entry_setup not being awaited."""
|
|
|
|
forward_event = asyncio.Event()
|
|
|
|
task: asyncio.Task | None = None
|
|
|
|
|
|
|
|
async def mock_setup_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock setting up entry."""
|
|
|
|
# Call async_forward_entry_setup without awaiting it
|
|
|
|
# This is not allowed and will raise a warning
|
|
|
|
nonlocal task
|
|
|
|
task = create_eager_task(
|
|
|
|
hass.config_entries.async_forward_entry_setup(entry, "light")
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def mock_unload_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock unloading an entry."""
|
|
|
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
|
|
|
assert result
|
|
|
|
return result
|
|
|
|
|
|
|
|
mock_remove_entry = AsyncMock(return_value=None)
|
|
|
|
|
|
|
|
async def mock_setup_entry_platform(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entry: config_entries.ConfigEntry,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
) -> None:
|
|
|
|
"""Mock setting up platform."""
|
|
|
|
await forward_event.wait()
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"test",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=mock_unload_entry,
|
|
|
|
async_remove_entry=mock_remove_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(
|
|
|
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
|
|
|
)
|
|
|
|
mock_platform(hass, "test.config_flow", None)
|
|
|
|
|
|
|
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
|
|
|
entry.add_to_manager(manager)
|
|
|
|
|
|
|
|
# Setup entry
|
|
|
|
await manager.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
forward_event.set()
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
await task
|
|
|
|
|
|
|
|
assert (
|
|
|
|
"Detected code that calls async_forward_entry_setup for integration "
|
|
|
|
"test with title: Mock Title and entry_id: test2, during setup without "
|
|
|
|
"awaiting async_forward_entry_setup, which can cause the setup lock "
|
|
|
|
"to be released before the setup is done. This will stop working in "
|
|
|
|
"Home Assistant 2025.1. Please report this issue."
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
) in caplog.text
|
|
|
|
|
|
|
|
|
|
|
|
async def test_config_entry_unloaded_during_platform_setup(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
) -> None:
|
2024-06-13 01:06:11 +00:00
|
|
|
"""Test async_forward_entry_setup not being awaited."""
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
task = None
|
|
|
|
|
|
|
|
async def mock_setup_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock setting up entry."""
|
|
|
|
|
2024-06-13 01:06:11 +00:00
|
|
|
# Call async_forward_entry_setup in a non-tracked task
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
# so we can unload the config entry during the setup
|
|
|
|
def _late_setup():
|
|
|
|
nonlocal task
|
|
|
|
task = asyncio.create_task(
|
2024-06-13 01:06:11 +00:00
|
|
|
hass.config_entries.async_forward_entry_setup(entry, "light")
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
hass.loop.call_soon(_late_setup)
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def mock_unload_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock unloading an entry."""
|
|
|
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
|
|
|
assert result
|
|
|
|
return result
|
|
|
|
|
|
|
|
mock_remove_entry = AsyncMock(return_value=None)
|
|
|
|
|
|
|
|
async def mock_setup_entry_platform(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entry: config_entries.ConfigEntry,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
) -> None:
|
|
|
|
"""Mock setting up platform."""
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"test",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=mock_unload_entry,
|
|
|
|
async_remove_entry=mock_remove_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(
|
|
|
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
|
|
|
)
|
|
|
|
mock_platform(hass, "test.config_flow", None)
|
|
|
|
|
|
|
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
|
|
|
entry.add_to_manager(manager)
|
|
|
|
|
|
|
|
# Setup entry
|
|
|
|
await manager.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
await manager.async_unload(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
del task
|
|
|
|
|
|
|
|
assert (
|
2024-06-14 06:00:36 +00:00
|
|
|
"OperationNotAllowed: The config entry 'Mock Title' (test) with "
|
|
|
|
"entry_id 'test2' cannot forward setup for light because it is "
|
|
|
|
"in state ConfigEntryState.NOT_LOADED, but needs to be in the "
|
|
|
|
"ConfigEntryState.LOADED state"
|
Ensure config entries are not unloaded while their platforms are setting up (#118767)
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* Report non-awaited/non-locked config entry platform forwards
Its currently possible for config entries to be reloaded while their platforms
are being forwarded if platform forwards are not awaited or done after the
config entry is setup since the lock will not be held in this case.
In https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards
we advised to await platform forwards to ensure this does not happen, however
for sleeping devices and late discovered devices, platform forwards may happen
later.
If config platform forwards are happening during setup, they should be awaited
If config entry platform forwards are not happening during setup, instead
async_late_forward_entry_setups should be used which will hold the lock to
prevent the config entry from being unloaded while its platforms are being
setup
* run with error on to find them
* cert_exp, hold lock
* cert_exp, hold lock
* shelly async_late_forward_entry_setups
* compact
* compact
* found another
* patch up mobileapp
* patch up hue tests
* patch up smartthings
* fix mqtt
* fix esphome
* zwave_js
* mqtt
* rework
* fixes
* fix mocking
* fix mocking
* do not call async_forward_entry_setup directly
* docstrings
* docstrings
* docstrings
* add comments
* doc strings
* fixed all in core, turn off strict
* coverage
* coverage
* missing
* coverage
2024-06-05 01:34:39 +00:00
|
|
|
) in caplog.text
|
2024-06-13 01:06:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_config_entry_late_platform_setup(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
manager: config_entries.ConfigEntries,
|
|
|
|
caplog: pytest.LogCaptureFixture,
|
|
|
|
) -> None:
|
|
|
|
"""Test async_forward_entry_setup not being awaited."""
|
|
|
|
task = None
|
|
|
|
|
|
|
|
async def mock_setup_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock setting up entry."""
|
|
|
|
|
|
|
|
# Call async_forward_entry_setup in a non-tracked task
|
|
|
|
# so we can unload the config entry during the setup
|
|
|
|
def _late_setup():
|
|
|
|
nonlocal task
|
|
|
|
task = asyncio.create_task(
|
|
|
|
hass.config_entries.async_forward_entry_setup(entry, "light")
|
|
|
|
)
|
|
|
|
|
|
|
|
hass.loop.call_soon(_late_setup)
|
|
|
|
return True
|
|
|
|
|
|
|
|
async def mock_unload_entry(
|
|
|
|
hass: HomeAssistant, entry: config_entries.ConfigEntry
|
|
|
|
) -> bool:
|
|
|
|
"""Mock unloading an entry."""
|
|
|
|
result = await hass.config_entries.async_unload_platforms(entry, ["light"])
|
|
|
|
assert result
|
|
|
|
return result
|
|
|
|
|
|
|
|
mock_remove_entry = AsyncMock(return_value=None)
|
|
|
|
|
|
|
|
async def mock_setup_entry_platform(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
entry: config_entries.ConfigEntry,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
) -> None:
|
|
|
|
"""Mock setting up platform."""
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
await asyncio.sleep(0)
|
|
|
|
|
|
|
|
mock_integration(
|
|
|
|
hass,
|
|
|
|
MockModule(
|
|
|
|
"test",
|
|
|
|
async_setup_entry=mock_setup_entry,
|
|
|
|
async_unload_entry=mock_unload_entry,
|
|
|
|
async_remove_entry=mock_remove_entry,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
mock_platform(
|
|
|
|
hass, "test.light", MockPlatform(async_setup_entry=mock_setup_entry_platform)
|
|
|
|
)
|
|
|
|
mock_platform(hass, "test.config_flow", None)
|
|
|
|
|
|
|
|
entry = MockConfigEntry(domain="test", entry_id="test2")
|
|
|
|
entry.add_to_manager(manager)
|
|
|
|
|
|
|
|
# Setup entry
|
|
|
|
await manager.async_setup(entry.entry_id)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
await task
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert (
|
|
|
|
"OperationNotAllowed: The config entry Mock Title (test) with "
|
|
|
|
"entry_id test2 cannot forward setup for light because it is "
|
|
|
|
"not loaded in the ConfigEntryState.NOT_LOADED state"
|
|
|
|
) not in caplog.text
|