data grand lyon: list stops and lines in config flow (#173117)
This commit is contained in:
@@ -5,7 +5,7 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import ClientError, ClientResponseError
|
from aiohttp import ClientError, ClientResponseError
|
||||||
from data_grand_lyon_ha import DataGrandLyonClient
|
from data_grand_lyon_ha import DataGrandLyonClient, TclStop, find_tcl_stop_by_id
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import (
|
from homeassistant.config_entries import (
|
||||||
@@ -18,6 +18,12 @@ from homeassistant.config_entries import (
|
|||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.selector import (
|
||||||
|
SelectOptionDict,
|
||||||
|
SelectSelector,
|
||||||
|
SelectSelectorConfig,
|
||||||
|
SelectSelectorMode,
|
||||||
|
)
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_LINE,
|
CONF_LINE,
|
||||||
@@ -43,13 +49,6 @@ STEP_RECONFIGURE_SCHEMA = vol.Schema(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
STEP_STOP_DATA_SCHEMA = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_LINE): str,
|
|
||||||
vol.Required(CONF_STOP_ID): vol.Coerce(int),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
STEP_VELOV_STATION_DATA_SCHEMA = vol.Schema(
|
STEP_VELOV_STATION_DATA_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_STATION_ID): vol.Coerce(int),
|
vol.Required(CONF_STATION_ID): vol.Coerce(int),
|
||||||
@@ -179,33 +178,126 @@ class DataGrandLyonConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
class StopSubentryFlowHandler(ConfigSubentryFlow):
|
class StopSubentryFlowHandler(ConfigSubentryFlow):
|
||||||
"""Handle a subentry flow for adding a Data Grand Lyon stop."""
|
"""Handle a subentry flow for adding a Data Grand Lyon stop."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Initialize the flow."""
|
||||||
|
self._stops: list[TclStop] = []
|
||||||
|
self._selected_stop: TclStop | None = None
|
||||||
|
self._selected_stop_id: int | None = None
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> SubentryFlowResult:
|
) -> SubentryFlowResult:
|
||||||
"""Handle the user step to add a new stop."""
|
"""Pick a stop from the list fetched from the API, or enter one manually."""
|
||||||
entry = self._get_entry()
|
if not self._stops:
|
||||||
|
if error := await self._async_load_stops():
|
||||||
|
return self.async_abort(reason=error)
|
||||||
|
|
||||||
|
errors: dict[str, str] = {}
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
line = user_input[CONF_LINE]
|
try:
|
||||||
stop_id = user_input[CONF_STOP_ID]
|
stop_id = int(user_input[CONF_STOP_ID])
|
||||||
unique_id = f"{line}_{stop_id}"
|
except ValueError:
|
||||||
|
errors[CONF_STOP_ID] = "invalid_stop_id"
|
||||||
|
else:
|
||||||
|
self._selected_stop_id = stop_id
|
||||||
|
self._selected_stop = find_tcl_stop_by_id(self._stops, stop_id)
|
||||||
|
return await self.async_step_pick_line()
|
||||||
|
|
||||||
for subentry in entry.subentries.values():
|
options = [
|
||||||
if subentry.unique_id == unique_id:
|
SelectOptionDict(value=str(stop.id), label=_stop_label(stop))
|
||||||
return self.async_abort(reason="already_configured")
|
for stop in sorted(
|
||||||
|
self._stops, key=lambda s: (s.nom, s.commune or "", s.id or 0)
|
||||||
name = f"{line} - Stop {stop_id}"
|
|
||||||
return self.async_create_entry(
|
|
||||||
title=name,
|
|
||||||
data={CONF_LINE: line, CONF_STOP_ID: stop_id},
|
|
||||||
unique_id=unique_id,
|
|
||||||
)
|
)
|
||||||
|
]
|
||||||
|
schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_STOP_ID): SelectSelector(
|
||||||
|
SelectSelectorConfig(
|
||||||
|
options=options,
|
||||||
|
mode=SelectSelectorMode.DROPDOWN,
|
||||||
|
sort=False,
|
||||||
|
custom_value=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user",
|
step_id="user",
|
||||||
data_schema=STEP_STOP_DATA_SCHEMA,
|
data_schema=schema,
|
||||||
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_step_pick_line(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> SubentryFlowResult:
|
||||||
|
"""Pick a line from the selected stop's desserte, or enter one manually."""
|
||||||
|
assert self._selected_stop_id is not None
|
||||||
|
if user_input is not None:
|
||||||
|
return self._create_stop(
|
||||||
|
line=user_input[CONF_LINE], stop_id=self._selected_stop_id
|
||||||
|
)
|
||||||
|
|
||||||
|
options = self._selected_stop.desserte if self._selected_stop else []
|
||||||
|
schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_LINE): SelectSelector(
|
||||||
|
SelectSelectorConfig(
|
||||||
|
options=options,
|
||||||
|
mode=SelectSelectorMode.DROPDOWN,
|
||||||
|
custom_value=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return self.async_show_form(step_id="pick_line", data_schema=schema)
|
||||||
|
|
||||||
|
async def _async_load_stops(self) -> str | None:
|
||||||
|
"""Fetch TCL stops from the API, returning an error key on failure."""
|
||||||
|
entry = self._get_entry()
|
||||||
|
session = async_get_clientsession(self.hass)
|
||||||
|
client = DataGrandLyonClient(
|
||||||
|
session=session,
|
||||||
|
username=entry.data[CONF_USERNAME],
|
||||||
|
password=entry.data[CONF_PASSWORD],
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self._stops = await client.get_tcl_stops()
|
||||||
|
except ClientResponseError as err:
|
||||||
|
if err.status in (401, 403):
|
||||||
|
return "invalid_auth"
|
||||||
|
return "cannot_connect"
|
||||||
|
except ClientError, TimeoutError:
|
||||||
|
return "cannot_connect"
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.exception("Unexpected error fetching Data Grand Lyon TCL stops")
|
||||||
|
return "unknown"
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _create_stop(self, line: str, stop_id: int) -> SubentryFlowResult:
|
||||||
|
"""Create the stop subentry, aborting on duplicate."""
|
||||||
|
entry = self._get_entry()
|
||||||
|
unique_id = f"{line}_{stop_id}"
|
||||||
|
for subentry in entry.subentries.values():
|
||||||
|
if subentry.unique_id == unique_id:
|
||||||
|
return self.async_abort(reason="already_configured")
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=f"{line} - Stop {stop_id}",
|
||||||
|
data={CONF_LINE: line, CONF_STOP_ID: stop_id},
|
||||||
|
unique_id=unique_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _stop_label(stop: TclStop) -> str:
|
||||||
|
label = stop.nom
|
||||||
|
# variable extracted to please codespell.
|
||||||
|
address = stop.adresse # codespell:ignore adresse
|
||||||
|
if address or stop.commune:
|
||||||
|
label += " (" + ", ".join(filter(None, [address, stop.commune])) + ")"
|
||||||
|
label += f" - {stop.id}"
|
||||||
|
|
||||||
|
return label
|
||||||
|
|
||||||
|
|
||||||
class VelovStationSubentryFlowHandler(ConfigSubentryFlow):
|
class VelovStationSubentryFlowHandler(ConfigSubentryFlow):
|
||||||
"""Handle a subentry flow for adding a Vélo'v station."""
|
"""Handle a subentry flow for adding a Vélo'v station."""
|
||||||
|
|||||||
@@ -46,17 +46,30 @@
|
|||||||
"config_subentries": {
|
"config_subentries": {
|
||||||
"stop": {
|
"stop": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
},
|
},
|
||||||
"entry_type": "Transit stop",
|
"entry_type": "Transit stop",
|
||||||
|
"error": {
|
||||||
|
"invalid_stop_id": "Stop ID must be a number."
|
||||||
|
},
|
||||||
"initiate_flow": {
|
"initiate_flow": {
|
||||||
"user": "Add transit stop"
|
"user": "Add transit stop"
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
|
"pick_line": {
|
||||||
|
"data": {
|
||||||
|
"line": "Line"
|
||||||
|
}
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"line": "Line",
|
"stop_id": "Stop"
|
||||||
"stop_id": "Stop ID"
|
},
|
||||||
|
"data_description": {
|
||||||
|
"stop_id": "Search by stop name, address or city, or enter a stop ID directly."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, patch
|
|||||||
from data_grand_lyon_ha import (
|
from data_grand_lyon_ha import (
|
||||||
TclPassage,
|
TclPassage,
|
||||||
TclPassageType,
|
TclPassageType,
|
||||||
|
TclStop,
|
||||||
VelovAvailabilityLevel,
|
VelovAvailabilityLevel,
|
||||||
VelovBikeStandAvailability,
|
VelovBikeStandAvailability,
|
||||||
VelovStation,
|
VelovStation,
|
||||||
@@ -50,6 +51,39 @@ MOCK_DEPARTURES = [
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MOCK_TCL_STOPS = [
|
||||||
|
TclStop(
|
||||||
|
id=100,
|
||||||
|
gid=1100,
|
||||||
|
adresse="Place Bellecour", # codespell:ignore adresse
|
||||||
|
ascenseur=False,
|
||||||
|
commune="Lyon 2",
|
||||||
|
desserte=["C3", "27"],
|
||||||
|
escalator=False,
|
||||||
|
insee="69382",
|
||||||
|
last_update=datetime(2026, 4, 10, 0, 0),
|
||||||
|
lat=45.757,
|
||||||
|
lon=4.832,
|
||||||
|
nom="Bellecour",
|
||||||
|
pmr=True,
|
||||||
|
),
|
||||||
|
TclStop(
|
||||||
|
id=200,
|
||||||
|
gid=1200,
|
||||||
|
adresse="Cours Lafayette", # codespell:ignore adresse
|
||||||
|
ascenseur=True,
|
||||||
|
commune="Lyon 3",
|
||||||
|
desserte=["C3", "T1"],
|
||||||
|
escalator=True,
|
||||||
|
insee="69383",
|
||||||
|
last_update=datetime(2026, 4, 10, 0, 0),
|
||||||
|
lat=45.763,
|
||||||
|
lon=4.846,
|
||||||
|
nom="Part-Dieu",
|
||||||
|
pmr=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
MOCK_VELOV_STATION = VelovStation(
|
MOCK_VELOV_STATION = VelovStation(
|
||||||
number=1001,
|
number=1001,
|
||||||
name="Place Bellecour",
|
name="Place Bellecour",
|
||||||
@@ -147,5 +181,6 @@ def mock_tcl_client() -> Generator[AsyncMock]:
|
|||||||
) as mock_cls:
|
) as mock_cls:
|
||||||
client = mock_cls.return_value
|
client = mock_cls.return_value
|
||||||
client.get_tcl_passages.return_value = MOCK_DEPARTURES
|
client.get_tcl_passages.return_value = MOCK_DEPARTURES
|
||||||
|
client.get_tcl_stops.return_value = MOCK_TCL_STOPS
|
||||||
client.get_velov_stations.return_value = [MOCK_VELOV_STATION]
|
client.get_velov_stations.return_value = [MOCK_VELOV_STATION]
|
||||||
yield client
|
yield client
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
|
from .conftest import MOCK_TCL_STOPS
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
@@ -32,6 +34,16 @@ def mock_get_tcl_passages() -> Generator[AsyncMock]:
|
|||||||
yield mock
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_get_tcl_stops() -> Generator[AsyncMock]:
|
||||||
|
"""Mock get_tcl_stops in the stop subentry picker flow."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.data_grand_lyon.config_flow.DataGrandLyonClient.get_tcl_stops",
|
||||||
|
return_value=MOCK_TCL_STOPS,
|
||||||
|
) as mock:
|
||||||
|
yield mock
|
||||||
|
|
||||||
|
|
||||||
# Main config flow tests
|
# Main config flow tests
|
||||||
|
|
||||||
|
|
||||||
@@ -274,11 +286,12 @@ async def test_reconfigure_flow_errors(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mock_subentries", [[]])
|
@pytest.mark.parametrize("mock_subentries", [[]])
|
||||||
async def test_stop_subentry_flow(
|
async def test_stop_subentry_picker_flow(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_get_tcl_stops: AsyncMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test adding a stop subentry."""
|
"""Test adding a stop subentry by picking a stop and a line from the lists."""
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@@ -289,23 +302,33 @@ async def test_stop_subentry_flow(
|
|||||||
)
|
)
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
assert result["step_id"] == "user"
|
assert result["step_id"] == "user"
|
||||||
|
assert mock_get_tcl_stops.await_count == 1
|
||||||
|
|
||||||
result = await hass.config_entries.subentries.async_configure(
|
result = await hass.config_entries.subentries.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{CONF_LINE: "C3", CONF_STOP_ID: 456},
|
{CONF_STOP_ID: "200"},
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "pick_line"
|
||||||
|
|
||||||
|
result = await hass.config_entries.subentries.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_LINE: "T1"},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result["title"] == "C3 - Stop 456"
|
assert result["title"] == "T1 - Stop 200"
|
||||||
assert result["data"] == {CONF_LINE: "C3", CONF_STOP_ID: 456}
|
assert result["data"] == {CONF_LINE: "T1", CONF_STOP_ID: 200}
|
||||||
assert result["unique_id"] == "C3_456"
|
assert result["unique_id"] == "T1_200"
|
||||||
|
|
||||||
|
|
||||||
async def test_stop_subentry_already_configured(
|
@pytest.mark.parametrize("mock_subentries", [[]])
|
||||||
|
async def test_stop_subentry_custom_value_flow(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_get_tcl_stops: AsyncMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test stop subentry aborts if same line+stop already exists."""
|
"""Test adding a stop subentry by typing a stop ID not present in the list."""
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@@ -314,16 +337,116 @@ async def test_stop_subentry_already_configured(
|
|||||||
(mock_config_entry.entry_id, SUBENTRY_TYPE_STOP),
|
(mock_config_entry.entry_id, SUBENTRY_TYPE_STOP),
|
||||||
context={"source": config_entries.SOURCE_USER},
|
context={"source": config_entries.SOURCE_USER},
|
||||||
)
|
)
|
||||||
|
result = await hass.config_entries.subentries.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_STOP_ID: "456"},
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "pick_line"
|
||||||
|
|
||||||
result = await hass.config_entries.subentries.async_configure(
|
result = await hass.config_entries.subentries.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{CONF_LINE: "C3", CONF_STOP_ID: 100},
|
{CONF_LINE: "C3"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "C3 - Stop 456"
|
||||||
|
assert result["data"] == {CONF_LINE: "C3", CONF_STOP_ID: 456}
|
||||||
|
assert result["unique_id"] == "C3_456"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mock_subentries", [[]])
|
||||||
|
async def test_stop_subentry_invalid_stop_id(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_get_tcl_stops: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test typing a non-numeric stop ID re-renders the form with an error."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
result = await hass.config_entries.subentries.async_init(
|
||||||
|
(mock_config_entry.entry_id, SUBENTRY_TYPE_STOP),
|
||||||
|
context={"source": config_entries.SOURCE_USER},
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.subentries.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_STOP_ID: "not-a-number"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {CONF_STOP_ID: "invalid_stop_id"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_stop_subentry_picker_already_configured(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_get_tcl_stops: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test picker stop subentry aborts if same line+stop already exists."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
result = await hass.config_entries.subentries.async_init(
|
||||||
|
(mock_config_entry.entry_id, SUBENTRY_TYPE_STOP),
|
||||||
|
context={"source": config_entries.SOURCE_USER},
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.subentries.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_STOP_ID: "100"},
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.subentries.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_LINE: "C3"},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["type"] is FlowResultType.ABORT
|
assert result["type"] is FlowResultType.ABORT
|
||||||
assert result["reason"] == "already_configured"
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("side_effect", "reason"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
ClientResponseError(request_info=None, history=(), status=500),
|
||||||
|
"cannot_connect",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ClientResponseError(request_info=None, history=(), status=401),
|
||||||
|
"invalid_auth",
|
||||||
|
),
|
||||||
|
(ClientConnectionError("boom"), "cannot_connect"),
|
||||||
|
(TimeoutError("boom"), "cannot_connect"),
|
||||||
|
(RuntimeError("boom"), "unknown"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("mock_subentries", [[]])
|
||||||
|
async def test_stop_subentry_picker_load_errors(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_get_tcl_stops: AsyncMock,
|
||||||
|
side_effect: Exception,
|
||||||
|
reason: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test picker aborts with the right reason when loading stops fails."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
mock_get_tcl_stops.side_effect = side_effect
|
||||||
|
|
||||||
|
result = await hass.config_entries.subentries.async_init(
|
||||||
|
(mock_config_entry.entry_id, SUBENTRY_TYPE_STOP),
|
||||||
|
context={"source": config_entries.SOURCE_USER},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == reason
|
||||||
|
|
||||||
|
|
||||||
# Vélo'v station subentry tests
|
# Vélo'v station subentry tests
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user