1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 21:06:19 +00:00

Adds ConfigFlow for London Underground (#152050)

Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
HarvsG
2025-10-08 15:17:34 +01:00
committed by GitHub
parent 56d953ac1e
commit 26437bb253
13 changed files with 703 additions and 36 deletions

View File

@@ -1 +1,36 @@
"""The london_underground component."""
from __future__ import annotations
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN as DOMAIN
from .coordinator import LondonTubeCoordinator, LondonUndergroundConfigEntry, TubeData
PLATFORMS: list[Platform] = [Platform.SENSOR]
async def async_setup_entry(
hass: HomeAssistant, entry: LondonUndergroundConfigEntry
) -> bool:
"""Set up London Underground from a config entry."""
session = async_get_clientsession(hass)
data = TubeData(session)
coordinator = LondonTubeCoordinator(hass, data, config_entry=entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
# Forward the setup to the sensor platform
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(
hass: HomeAssistant, entry: LondonUndergroundConfigEntry
) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -0,0 +1,152 @@
"""Config flow for London Underground integration."""
from __future__ import annotations
import asyncio
import logging
from typing import Any
from london_tube_status import TubeData
import voluptuous as vol
from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlowWithReload,
)
from homeassistant.core import callback
from homeassistant.helpers import selector
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType
from .const import CONF_LINE, DEFAULT_LINES, DOMAIN, TUBE_LINES
_LOGGER = logging.getLogger(__name__)
class LondonUndergroundConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for London Underground."""
VERSION = 1
MINOR_VERSION = 1
@staticmethod
@callback
def async_get_options_flow(
_: ConfigEntry,
) -> LondonUndergroundOptionsFlow:
"""Get the options flow for this handler."""
return LondonUndergroundOptionsFlow()
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
session = async_get_clientsession(self.hass)
data = TubeData(session)
try:
async with asyncio.timeout(10):
await data.update()
except TimeoutError:
errors["base"] = "timeout_connect"
except Exception:
_LOGGER.exception("Unexpected error")
errors["base"] = "cannot_connect"
else:
return self.async_create_entry(
title="London Underground",
data={},
options={CONF_LINE: user_input.get(CONF_LINE, DEFAULT_LINES)},
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Optional(
CONF_LINE,
default=DEFAULT_LINES,
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=TUBE_LINES,
multiple=True,
mode=selector.SelectSelectorMode.DROPDOWN,
)
),
}
),
errors=errors,
)
async def async_step_import(self, import_data: ConfigType) -> ConfigFlowResult:
"""Handle import from configuration.yaml."""
session = async_get_clientsession(self.hass)
data = TubeData(session)
try:
async with asyncio.timeout(10):
await data.update()
except Exception:
_LOGGER.exception(
"Unexpected error trying to connect before importing config, aborting import "
)
return self.async_abort(reason="cannot_connect")
_LOGGER.warning(
"Importing London Underground config from configuration.yaml: %s",
import_data,
)
# Extract lines from the sensor platform config
lines = import_data.get(CONF_LINE, DEFAULT_LINES)
if "London Overground" in lines:
_LOGGER.warning(
"London Overground was removed from the configuration as the line has been divided and renamed"
)
lines.remove("London Overground")
return self.async_create_entry(
title="London Underground",
data={},
options={CONF_LINE: import_data.get(CONF_LINE, DEFAULT_LINES)},
)
class LondonUndergroundOptionsFlow(OptionsFlowWithReload):
"""Handle options."""
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage the options."""
if user_input is not None:
_LOGGER.debug(
"Updating london underground with options flow user_input: %s",
user_input,
)
return self.async_create_entry(
title="",
data={CONF_LINE: user_input[CONF_LINE]},
)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Optional(
CONF_LINE,
default=self.config_entry.options.get(
CONF_LINE,
self.config_entry.data.get(CONF_LINE, DEFAULT_LINES),
),
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=TUBE_LINES,
multiple=True,
mode=selector.SelectSelectorMode.DROPDOWN,
)
),
}
),
)

View File

@@ -6,7 +6,6 @@ DOMAIN = "london_underground"
CONF_LINE = "line"
SCAN_INTERVAL = timedelta(seconds=30)
TUBE_LINES = [
@@ -18,7 +17,7 @@ TUBE_LINES = [
"Elizabeth line",
"Hammersmith & City",
"Jubilee",
"London Overground",
"London Overground", # no longer supported
"Metropolitan",
"Northern",
"Piccadilly",
@@ -31,3 +30,20 @@ TUBE_LINES = [
"Weaver",
"Windrush",
]
# Default lines to monitor if none selected
DEFAULT_LINES = [
"Bakerloo",
"Central",
"Circle",
"District",
"DLR",
"Elizabeth line",
"Hammersmith & City",
"Jubilee",
"Metropolitan",
"Northern",
"Piccadilly",
"Victoria",
"Waterloo & City",
]

View File

@@ -8,6 +8,7 @@ from typing import cast
from london_tube_status import TubeData
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@@ -15,16 +16,23 @@ from .const import DOMAIN, SCAN_INTERVAL
_LOGGER = logging.getLogger(__name__)
type LondonUndergroundConfigEntry = ConfigEntry[LondonTubeCoordinator]
class LondonTubeCoordinator(DataUpdateCoordinator[dict[str, dict[str, str]]]):
"""London Underground sensor coordinator."""
def __init__(self, hass: HomeAssistant, data: TubeData) -> None:
def __init__(
self,
hass: HomeAssistant,
data: TubeData,
config_entry: LondonUndergroundConfigEntry,
) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=None,
config_entry=config_entry,
name=DOMAIN,
update_interval=SCAN_INTERVAL,
)

View File

@@ -2,9 +2,12 @@
"domain": "london_underground",
"name": "London Underground",
"codeowners": ["@jpbede"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/london_underground",
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["london_tube_status"],
"quality_scale": "legacy",
"requirements": ["london-tube-status==0.5"]
"requirements": ["london-tube-status==0.5"],
"single_config_entry": true
}

View File

@@ -5,23 +5,26 @@ from __future__ import annotations
import logging
from typing import Any
from london_tube_status import TubeData
import voluptuous as vol
from homeassistant.components.sensor import (
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
SensorEntity,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import CONF_LINE, TUBE_LINES
from .coordinator import LondonTubeCoordinator
from .const import CONF_LINE, DOMAIN, TUBE_LINES
from .coordinator import LondonTubeCoordinator, LondonUndergroundConfigEntry
_LOGGER = logging.getLogger(__name__)
@@ -38,18 +41,54 @@ async def async_setup_platform(
) -> None:
"""Set up the Tube sensor."""
session = async_get_clientsession(hass)
# If configuration.yaml config exists, trigger the import flow.
# If the config entry already exists, this will not be triggered as only one config is allowed.
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
)
if (
result.get("type") is FlowResultType.ABORT
and result.get("reason") != "already_configured"
):
ir.async_create_issue(
hass,
DOMAIN,
f"deprecated_yaml_import_issue_{result.get('reason')}",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_yaml_import_issue",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "London Underground",
},
)
return
data = TubeData(session)
coordinator = LondonTubeCoordinator(hass, data)
ir.async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
"deprecated_yaml",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "London Underground",
},
)
await coordinator.async_refresh()
if not coordinator.last_update_success:
raise PlatformNotReady
async def async_setup_entry(
hass: HomeAssistant,
entry: LondonUndergroundConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the London Underground sensor from config entry."""
async_add_entities(
LondonTubeSensor(coordinator, line) for line in config[CONF_LINE]
LondonTubeSensor(entry.runtime_data, line) for line in entry.options[CONF_LINE]
)
@@ -58,11 +97,21 @@ class LondonTubeSensor(CoordinatorEntity[LondonTubeCoordinator], SensorEntity):
_attr_attribution = "Powered by TfL Open Data"
_attr_icon = "mdi:subway"
_attr_has_entity_name = True # Use modern entity naming
def __init__(self, coordinator: LondonTubeCoordinator, name: str) -> None:
"""Initialize the London Underground sensor."""
super().__init__(coordinator)
self._name = name
# Add unique_id for proper entity registry
self._attr_unique_id = f"tube_{name.lower().replace(' ', '_')}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, DOMAIN)},
name="London Underground",
manufacturer="Transport for London",
model="Tube Status",
entry_type=DeviceEntryType.SERVICE,
)
@property
def name(self) -> str:

View File

@@ -0,0 +1,38 @@
{
"config": {
"step": {
"user": {
"title": "Set up London Underground",
"description": "Select which tube lines you want to monitor",
"data": {
"line": "Tube lines"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
}
},
"options": {
"step": {
"init": {
"title": "Configure London Underground",
"description": "[%key:component::london_underground::config::step::user::description%]",
"data": {
"line": "[%key:component::london_underground::config::step::user::data::line%]"
}
}
}
},
"issues": {
"deprecated_yaml_import_issue": {
"title": "London Underground YAML configuration deprecated",
"description": "Configuring London Underground using YAML sensor platform is deprecated.\n\nWhile importing your configuration, an error occurred when trying to connect to the Transport for London API. Please restart Home Assistant to try again, or remove the existing YAML configuration and set the integration up via the UI."
}
}
}

View File

@@ -367,6 +367,7 @@ FLOWS = {
"local_ip",
"local_todo",
"locative",
"london_underground",
"lookin",
"loqed",
"luftdaten",

View File

@@ -3688,9 +3688,10 @@
},
"london_underground": {
"name": "London Underground",
"integration_type": "hub",
"config_flow": false,
"iot_class": "cloud_polling"
"integration_type": "service",
"config_flow": true,
"iot_class": "cloud_polling",
"single_config_entry": true
},
"lookin": {
"name": "LOOKin",

View File

@@ -0,0 +1,65 @@
"""Fixtures for the london_underground tests."""
from collections.abc import AsyncGenerator
import json
from unittest.mock import AsyncMock, patch
from london_tube_status import parse_api_response
import pytest
from homeassistant.components.london_underground.const import CONF_LINE, DOMAIN
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, async_load_fixture
from tests.conftest import AiohttpClientMocker
@pytest.fixture
def mock_setup_entry():
"""Prevent setup of integration during tests."""
with patch(
"homeassistant.components.london_underground.async_setup_entry",
return_value=True,
) as mock_setup:
yield mock_setup
@pytest.fixture
async def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
"""Mock the config entry."""
entry = MockConfigEntry(
domain=DOMAIN,
data={},
options={CONF_LINE: ["Metropolitan"]},
title="London Underground",
)
# Add and set up the entry
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
return entry
@pytest.fixture
async def mock_london_underground_client(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
) -> AsyncGenerator[AsyncMock]:
"""Mock a London Underground client."""
with (
patch(
"homeassistant.components.london_underground.TubeData",
autospec=True,
) as mock_client,
patch(
"homeassistant.components.london_underground.config_flow.TubeData",
new=mock_client,
),
):
client = mock_client.return_value
# Load the fixture text
fixture_text = await async_load_fixture(hass, "line_status.json", DOMAIN)
fixture_data = parse_api_response(json.loads(fixture_text))
client.data = fixture_data
yield client

View File

@@ -0,0 +1,186 @@
"""Test the London Underground config flow."""
import asyncio
import pytest
from homeassistant.components.london_underground.const import (
CONF_LINE,
DEFAULT_LINES,
DOMAIN,
)
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import issue_registry as ir
async def test_validate_input_success(
hass: HomeAssistant, mock_setup_entry, mock_london_underground_client
) -> None:
"""Test successful validation of TfL API."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_LINE: ["Bakerloo", "Central"]},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "London Underground"
assert result["data"] == {}
assert result["options"] == {CONF_LINE: ["Bakerloo", "Central"]}
async def test_options(
hass: HomeAssistant, mock_setup_entry, mock_config_entry
) -> None:
"""Test updating options."""
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
CONF_LINE: ["Bakerloo", "Central"],
},
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_LINE: ["Bakerloo", "Central"],
}
@pytest.mark.parametrize(
("side_effect", "expected_error"),
[
(Exception, "cannot_connect"),
(asyncio.TimeoutError, "timeout_connect"),
],
)
async def test_validate_input_exceptions(
hass: HomeAssistant,
mock_setup_entry,
mock_london_underground_client,
side_effect,
expected_error,
) -> None:
"""Test validation with connection and timeout errors."""
mock_london_underground_client.update.side_effect = side_effect
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_LINE: ["Bakerloo", "Central"]},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"]["base"] == expected_error
# confirm recovery after error
mock_london_underground_client.update.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "London Underground"
assert result["data"] == {}
assert result["options"] == {CONF_LINE: DEFAULT_LINES}
async def test_already_configured(
hass: HomeAssistant,
mock_london_underground_client,
mock_setup_entry,
mock_config_entry,
) -> None:
"""Try (and fail) setting up a config entry when one already exists."""
# Try to start the flow
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"
async def test_yaml_import(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
mock_london_underground_client,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test a YAML sensor is imported and becomes an operational config entry."""
# Set up via YAML which will trigger import and set up the config entry
IMPORT_DATA = {
"platform": "london_underground",
"line": ["Central", "Piccadilly", "Victoria", "Bakerloo", "Northern"],
}
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_DATA
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "London Underground"
assert result["data"] == {}
assert result["options"] == {
CONF_LINE: ["Central", "Piccadilly", "Victoria", "Bakerloo", "Northern"]
}
async def test_failed_yaml_import_connection(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
mock_london_underground_client,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test a YAML sensor is imported and becomes an operational config entry."""
# Set up via YAML which will trigger import and set up the config entry
mock_london_underground_client.update.side_effect = asyncio.TimeoutError
IMPORT_DATA = {
"platform": "london_underground",
"line": ["Central", "Piccadilly", "Victoria", "Bakerloo", "Northern"],
}
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_DATA
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "cannot_connect"
async def test_failed_yaml_import_already_configured(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
mock_london_underground_client,
caplog: pytest.LogCaptureFixture,
mock_config_entry,
) -> None:
"""Test a YAML sensor is imported and becomes an operational config entry."""
# Set up via YAML which will trigger import and set up the config entry
IMPORT_DATA = {
"platform": "london_underground",
"line": ["Central", "Piccadilly", "Victoria", "Bakerloo", "Northern"],
}
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=IMPORT_DATA
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"

View File

@@ -0,0 +1,20 @@
"""Test the London Underground init."""
from homeassistant.core import HomeAssistant
async def test_reload_entry(
hass: HomeAssistant, mock_london_underground_client, mock_config_entry
) -> None:
"""Test reloading the config entry."""
# Test reloading with updated options
hass.config_entries.async_update_entry(
mock_config_entry,
data={},
options={"line": ["Bakerloo", "Central"]},
)
await hass.async_block_till_done()
# Verify that setup was called for each reload
assert len(mock_london_underground_client.mock_calls) > 0

View File

@@ -1,37 +1,130 @@
"""The tests for the london_underground platform."""
from london_tube_status import API_URL
import asyncio
import pytest
from homeassistant.components.london_underground.const import CONF_LINE, DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.setup import async_setup_component
from tests.common import async_load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker
VALID_CONFIG = {
"sensor": {"platform": "london_underground", CONF_LINE: ["Metropolitan"]}
}
async def test_valid_state(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
mock_london_underground_client,
mock_config_entry,
) -> None:
"""Test for operational london_underground sensor with proper attributes."""
aioclient_mock.get(
API_URL,
text=await async_load_fixture(hass, "line_status.json", DOMAIN),
)
"""Test operational London Underground sensor using a mock config entry."""
# Ensure the entry is fully loaded
assert mock_config_entry.state is ConfigEntryState.LOADED
# Confirm that the expected entity exists and is correct
state = hass.states.get("sensor.london_underground_metropolitan")
assert state is not None
assert state.state == "Good Service"
assert state.attributes == {
"Description": "Nothing to report",
"attribution": "Powered by TfL Open Data",
"friendly_name": "London Underground Metropolitan",
"icon": "mdi:subway",
}
# No YAML warning should be issued, since setup was not via YAML
assert not issue_registry.async_get_issue(DOMAIN, "yaml_deprecated")
async def test_yaml_import(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
mock_london_underground_client,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test a YAML sensor is imported and becomes an operational config entry."""
# Set up via YAML which will trigger import and set up the config entry
VALID_CONFIG = {
"sensor": {
"platform": "london_underground",
CONF_LINE: ["Metropolitan", "London Overground"],
}
}
assert await async_setup_component(hass, "sensor", VALID_CONFIG)
await hass.async_block_till_done()
state = hass.states.get("sensor.metropolitan")
# Verify the config entry was created
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
# Verify a warning was issued about YAML deprecation
assert issue_registry.async_get_issue(HOMEASSISTANT_DOMAIN, "deprecated_yaml")
# Check the state after setup completes
state = hass.states.get("sensor.london_underground_metropolitan")
assert state
assert state.state == "Good Service"
assert state.attributes == {
"Description": "Nothing to report",
"attribution": "Powered by TfL Open Data",
"friendly_name": "Metropolitan",
"friendly_name": "London Underground Metropolitan",
"icon": "mdi:subway",
}
# Since being renamed London overground is no longer returned by the API
# So check that we do not import it and that we warn the user
state = hass.states.get("sensor.london_underground_london_overground")
assert not state
assert any(
"London Overground was removed from the configuration as the line has been divided and renamed"
in record.message
for record in caplog.records
)
async def test_failed_yaml_import(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
mock_london_underground_client,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test a YAML sensor is imported and becomes an operational config entry."""
# Set up via YAML which will trigger import and set up the config entry
mock_london_underground_client.update.side_effect = asyncio.TimeoutError
VALID_CONFIG = {
"sensor": {"platform": "london_underground", CONF_LINE: ["Metropolitan"]}
}
assert await async_setup_component(hass, "sensor", VALID_CONFIG)
await hass.async_block_till_done()
# Verify the config entry was not created
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 0
# verify no flows still in progress
flows = hass.config_entries.flow.async_progress()
assert len(flows) == 0
assert any(
"Unexpected error trying to connect before importing config" in record.message
for record in caplog.records
)
# Confirm that the import did not happen
assert not any(
"Importing London Underground config from configuration.yaml" in record.message
for record in caplog.records
)
assert not any(
"migrated to a config entry and can be safely removed" in record.message
for record in caplog.records
)
# Verify a warning was issued about YAML not being imported
assert issue_registry.async_get_issue(
DOMAIN, "deprecated_yaml_import_issue_cannot_connect"
)