mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
Do not link nextbus coordinator to config entry (#128151)
* Do not link nextbus coordinator to config entry * Refactor tests and add specific failure test * Use ConfigEntryNotReady * Cleanup coordinator
This commit is contained in:
parent
d15a9a4359
commit
7e56b595a0
@ -3,6 +3,7 @@
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_STOP, Platform
|
from homeassistant.const import CONF_STOP, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
|
||||||
from .const import CONF_AGENCY, CONF_ROUTE, DOMAIN
|
from .const import CONF_AGENCY, CONF_ROUTE, DOMAIN
|
||||||
from .coordinator import NextBusDataUpdateCoordinator
|
from .coordinator import NextBusDataUpdateCoordinator
|
||||||
@ -27,7 +28,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
coordinator.add_stop_route(entry_stop, entry.data[CONF_ROUTE])
|
coordinator.add_stop_route(entry_stop, entry.data[CONF_ROUTE])
|
||||||
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_refresh()
|
||||||
|
if not coordinator.last_update_success:
|
||||||
|
raise ConfigEntryNotReady from coordinator.last_exception
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ class NextBusDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
|
config_entry=None, # It is shared between multiple entries
|
||||||
name=DOMAIN,
|
name=DOMAIN,
|
||||||
update_interval=timedelta(seconds=30),
|
update_interval=timedelta(seconds=30),
|
||||||
)
|
)
|
||||||
@ -48,13 +49,6 @@ class NextBusDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
"""Check if this coordinator is tracking any routes."""
|
"""Check if this coordinator is tracking any routes."""
|
||||||
return len(self._route_stops) > 0
|
return len(self._route_stops) > 0
|
||||||
|
|
||||||
async def async_shutdown(self) -> None:
|
|
||||||
"""If there are no more routes, cancel any scheduled call, and ignore new runs."""
|
|
||||||
if self.has_routes():
|
|
||||||
return
|
|
||||||
|
|
||||||
await super().async_shutdown()
|
|
||||||
|
|
||||||
async def _async_update_data(self) -> dict[str, Any]:
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
"""Fetch data from NextBus."""
|
"""Fetch data from NextBus."""
|
||||||
|
|
||||||
|
@ -1 +1,34 @@
|
|||||||
"""The tests for the nexbus component."""
|
"""The tests for the nexbus component."""
|
||||||
|
|
||||||
|
from homeassistant.components.nextbus.const import CONF_AGENCY, CONF_ROUTE, DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import CONF_STOP
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import VALID_AGENCY_TITLE, VALID_ROUTE_TITLE, VALID_STOP_TITLE
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def assert_setup_sensor(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: dict[str, dict[str, str]],
|
||||||
|
expected_state=ConfigEntryState.LOADED,
|
||||||
|
route_title: str = VALID_ROUTE_TITLE,
|
||||||
|
) -> MockConfigEntry:
|
||||||
|
"""Set up the sensor and assert it's been created."""
|
||||||
|
unique_id = f"{config[DOMAIN][CONF_AGENCY]}_{config[DOMAIN][CONF_ROUTE]}_{config[DOMAIN][CONF_STOP]}"
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data=config[DOMAIN],
|
||||||
|
title=f"{VALID_AGENCY_TITLE} {route_title} {VALID_STOP_TITLE}",
|
||||||
|
unique_id=unique_id,
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert config_entry.state is expected_state
|
||||||
|
|
||||||
|
return config_entry
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
"""Test helpers for NextBus tests."""
|
"""Test helpers for NextBus tests."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from .const import BASIC_RESULTS
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(
|
@pytest.fixture(
|
||||||
params=[
|
params=[
|
||||||
@ -128,3 +131,21 @@ def mock_nextbus_lists(
|
|||||||
instance.route_details.side_effect = route_details_side_effect
|
instance.route_details.side_effect = route_details_side_effect
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_nextbus() -> Generator[MagicMock]:
|
||||||
|
"""Create a mock py_nextbus module."""
|
||||||
|
with patch("homeassistant.components.nextbus.coordinator.NextBusClient") as client:
|
||||||
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_nextbus_predictions(
|
||||||
|
mock_nextbus: MagicMock,
|
||||||
|
) -> Generator[MagicMock]:
|
||||||
|
"""Create a mock of NextBusClient predictions."""
|
||||||
|
instance = mock_nextbus.return_value
|
||||||
|
instance.predictions_for_stop.return_value = BASIC_RESULTS
|
||||||
|
|
||||||
|
return instance.predictions_for_stop
|
||||||
|
101
tests/components/nextbus/const.py
Normal file
101
tests/components/nextbus/const.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
"""Constants for NextBus tests."""
|
||||||
|
|
||||||
|
from homeassistant.components.nextbus.const import CONF_AGENCY, CONF_ROUTE, DOMAIN
|
||||||
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
|
from homeassistant.const import CONF_STOP
|
||||||
|
|
||||||
|
VALID_AGENCY = "sfmta-cis"
|
||||||
|
VALID_ROUTE = "F"
|
||||||
|
VALID_STOP = "5184"
|
||||||
|
VALID_COORDINATOR_KEY = f"{VALID_AGENCY}-{VALID_STOP}"
|
||||||
|
VALID_AGENCY_TITLE = "San Francisco Muni"
|
||||||
|
VALID_ROUTE_TITLE = "F-Market & Wharves"
|
||||||
|
VALID_STOP_TITLE = "Market St & 7th St"
|
||||||
|
SENSOR_ID = "sensor.san_francisco_muni_f_market_wharves_market_st_7th_st"
|
||||||
|
|
||||||
|
ROUTE_2 = "G"
|
||||||
|
ROUTE_TITLE_2 = "G-Market & Wharves"
|
||||||
|
SENSOR_ID_2 = "sensor.san_francisco_muni_g_market_wharves_market_st_7th_st"
|
||||||
|
|
||||||
|
PLATFORM_CONFIG = {
|
||||||
|
SENSOR_DOMAIN: {
|
||||||
|
"platform": DOMAIN,
|
||||||
|
CONF_AGENCY: VALID_AGENCY,
|
||||||
|
CONF_ROUTE: VALID_ROUTE,
|
||||||
|
CONF_STOP: VALID_STOP,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_BASIC = {
|
||||||
|
DOMAIN: {
|
||||||
|
CONF_AGENCY: VALID_AGENCY,
|
||||||
|
CONF_ROUTE: VALID_ROUTE,
|
||||||
|
CONF_STOP: VALID_STOP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_BASIC_2 = {
|
||||||
|
DOMAIN: {
|
||||||
|
CONF_AGENCY: VALID_AGENCY,
|
||||||
|
CONF_ROUTE: ROUTE_2,
|
||||||
|
CONF_STOP: VALID_STOP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BASIC_RESULTS = [
|
||||||
|
{
|
||||||
|
"route": {
|
||||||
|
"title": VALID_ROUTE_TITLE,
|
||||||
|
"id": VALID_ROUTE,
|
||||||
|
},
|
||||||
|
"stop": {
|
||||||
|
"name": VALID_STOP_TITLE,
|
||||||
|
"id": VALID_STOP,
|
||||||
|
},
|
||||||
|
"values": [
|
||||||
|
{"minutes": 1, "timestamp": 1553807371000},
|
||||||
|
{"minutes": 2, "timestamp": 1553807372000},
|
||||||
|
{"minutes": 3, "timestamp": 1553807373000},
|
||||||
|
{"minutes": 10, "timestamp": 1553807380000},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": {
|
||||||
|
"title": ROUTE_TITLE_2,
|
||||||
|
"id": ROUTE_2,
|
||||||
|
},
|
||||||
|
"stop": {
|
||||||
|
"name": VALID_STOP_TITLE,
|
||||||
|
"id": VALID_STOP,
|
||||||
|
},
|
||||||
|
"values": [
|
||||||
|
{"minutes": 90, "timestamp": 1553807379000},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
NO_UPCOMING = [
|
||||||
|
{
|
||||||
|
"route": {
|
||||||
|
"title": VALID_ROUTE_TITLE,
|
||||||
|
"id": VALID_ROUTE,
|
||||||
|
},
|
||||||
|
"stop": {
|
||||||
|
"name": VALID_STOP_TITLE,
|
||||||
|
"id": VALID_STOP,
|
||||||
|
},
|
||||||
|
"values": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"route": {
|
||||||
|
"title": ROUTE_TITLE_2,
|
||||||
|
"id": ROUTE_2,
|
||||||
|
},
|
||||||
|
"stop": {
|
||||||
|
"name": VALID_STOP_TITLE,
|
||||||
|
"id": VALID_STOP,
|
||||||
|
},
|
||||||
|
"values": [],
|
||||||
|
},
|
||||||
|
]
|
27
tests/components/nextbus/test_init.py
Normal file
27
tests/components/nextbus/test_init.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"""The tests for the nexbus sensor component."""
|
||||||
|
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
from urllib.error import HTTPError
|
||||||
|
|
||||||
|
from homeassistant.components.nextbus.coordinator import NextBusHTTPError
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from . import assert_setup_sensor
|
||||||
|
from .const import CONFIG_BASIC
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_retry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_nextbus: MagicMock,
|
||||||
|
mock_nextbus_lists: MagicMock,
|
||||||
|
mock_nextbus_predictions: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Verify that a list of messages are rendered correctly."""
|
||||||
|
|
||||||
|
mock_nextbus_predictions.side_effect = NextBusHTTPError(
|
||||||
|
"failed", HTTPError("url", 500, "error", MagicMock(), None)
|
||||||
|
)
|
||||||
|
await assert_setup_sensor(
|
||||||
|
hass, CONFIG_BASIC, expected_state=ConfigEntryState.SETUP_RETRY
|
||||||
|
)
|
@ -1,161 +1,36 @@
|
|||||||
"""The tests for the nexbus sensor component."""
|
"""The tests for the nexbus sensor component."""
|
||||||
|
|
||||||
from collections.abc import Generator
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock
|
||||||
from urllib.error import HTTPError
|
from urllib.error import HTTPError
|
||||||
|
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
from py_nextbus.client import NextBusFormatError, NextBusHTTPError
|
from py_nextbus.client import NextBusFormatError, NextBusHTTPError
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import sensor
|
from homeassistant.components.nextbus.const import DOMAIN
|
||||||
from homeassistant.components.nextbus.const import CONF_AGENCY, CONF_ROUTE, DOMAIN
|
|
||||||
from homeassistant.components.nextbus.coordinator import NextBusDataUpdateCoordinator
|
from homeassistant.components.nextbus.coordinator import NextBusDataUpdateCoordinator
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import CONF_NAME, CONF_STOP
|
from homeassistant.const import CONF_NAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.update_coordinator import UpdateFailed
|
from homeassistant.helpers.update_coordinator import UpdateFailed
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from . import assert_setup_sensor
|
||||||
|
from .const import (
|
||||||
|
BASIC_RESULTS,
|
||||||
|
CONFIG_BASIC,
|
||||||
|
CONFIG_BASIC_2,
|
||||||
|
NO_UPCOMING,
|
||||||
|
ROUTE_TITLE_2,
|
||||||
|
SENSOR_ID,
|
||||||
|
SENSOR_ID_2,
|
||||||
|
VALID_AGENCY,
|
||||||
|
VALID_COORDINATOR_KEY,
|
||||||
|
VALID_ROUTE_TITLE,
|
||||||
|
VALID_STOP_TITLE,
|
||||||
|
)
|
||||||
|
|
||||||
VALID_AGENCY = "sfmta-cis"
|
from tests.common import async_fire_time_changed
|
||||||
VALID_ROUTE = "F"
|
|
||||||
VALID_STOP = "5184"
|
|
||||||
VALID_COORDINATOR_KEY = f"{VALID_AGENCY}-{VALID_STOP}"
|
|
||||||
VALID_AGENCY_TITLE = "San Francisco Muni"
|
|
||||||
VALID_ROUTE_TITLE = "F-Market & Wharves"
|
|
||||||
VALID_STOP_TITLE = "Market St & 7th St"
|
|
||||||
SENSOR_ID = "sensor.san_francisco_muni_f_market_wharves_market_st_7th_st"
|
|
||||||
|
|
||||||
ROUTE_2 = "G"
|
|
||||||
ROUTE_TITLE_2 = "G-Market & Wharves"
|
|
||||||
SENSOR_ID_2 = "sensor.san_francisco_muni_g_market_wharves_market_st_7th_st"
|
|
||||||
|
|
||||||
PLATFORM_CONFIG = {
|
|
||||||
sensor.DOMAIN: {
|
|
||||||
"platform": DOMAIN,
|
|
||||||
CONF_AGENCY: VALID_AGENCY,
|
|
||||||
CONF_ROUTE: VALID_ROUTE,
|
|
||||||
CONF_STOP: VALID_STOP,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_BASIC = {
|
|
||||||
DOMAIN: {
|
|
||||||
CONF_AGENCY: VALID_AGENCY,
|
|
||||||
CONF_ROUTE: VALID_ROUTE,
|
|
||||||
CONF_STOP: VALID_STOP,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CONFIG_BASIC_2 = {
|
|
||||||
DOMAIN: {
|
|
||||||
CONF_AGENCY: VALID_AGENCY,
|
|
||||||
CONF_ROUTE: ROUTE_2,
|
|
||||||
CONF_STOP: VALID_STOP,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BASIC_RESULTS = [
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"title": VALID_ROUTE_TITLE,
|
|
||||||
"id": VALID_ROUTE,
|
|
||||||
},
|
|
||||||
"stop": {
|
|
||||||
"name": VALID_STOP_TITLE,
|
|
||||||
"id": VALID_STOP,
|
|
||||||
},
|
|
||||||
"values": [
|
|
||||||
{"minutes": 1, "timestamp": 1553807371000},
|
|
||||||
{"minutes": 2, "timestamp": 1553807372000},
|
|
||||||
{"minutes": 3, "timestamp": 1553807373000},
|
|
||||||
{"minutes": 10, "timestamp": 1553807380000},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"title": ROUTE_TITLE_2,
|
|
||||||
"id": ROUTE_2,
|
|
||||||
},
|
|
||||||
"stop": {
|
|
||||||
"name": VALID_STOP_TITLE,
|
|
||||||
"id": VALID_STOP,
|
|
||||||
},
|
|
||||||
"values": [
|
|
||||||
{"minutes": 90, "timestamp": 1553807379000},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
NO_UPCOMING = [
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"title": VALID_ROUTE_TITLE,
|
|
||||||
"id": VALID_ROUTE,
|
|
||||||
},
|
|
||||||
"stop": {
|
|
||||||
"name": VALID_STOP_TITLE,
|
|
||||||
"id": VALID_STOP,
|
|
||||||
},
|
|
||||||
"values": [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"title": ROUTE_TITLE_2,
|
|
||||||
"id": ROUTE_2,
|
|
||||||
},
|
|
||||||
"stop": {
|
|
||||||
"name": VALID_STOP_TITLE,
|
|
||||||
"id": VALID_STOP,
|
|
||||||
},
|
|
||||||
"values": [],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_nextbus() -> Generator[MagicMock]:
|
|
||||||
"""Create a mock py_nextbus module."""
|
|
||||||
with patch("homeassistant.components.nextbus.coordinator.NextBusClient") as client:
|
|
||||||
yield client
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_nextbus_predictions(
|
|
||||||
mock_nextbus: MagicMock,
|
|
||||||
) -> Generator[MagicMock]:
|
|
||||||
"""Create a mock of NextBusClient predictions."""
|
|
||||||
instance = mock_nextbus.return_value
|
|
||||||
instance.predictions_for_stop.return_value = BASIC_RESULTS
|
|
||||||
|
|
||||||
return instance.predictions_for_stop
|
|
||||||
|
|
||||||
|
|
||||||
async def assert_setup_sensor(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
config: dict[str, dict[str, str]],
|
|
||||||
expected_state=ConfigEntryState.LOADED,
|
|
||||||
route_title: str = VALID_ROUTE_TITLE,
|
|
||||||
) -> MockConfigEntry:
|
|
||||||
"""Set up the sensor and assert it's been created."""
|
|
||||||
unique_id = f"{config[DOMAIN][CONF_AGENCY]}_{config[DOMAIN][CONF_ROUTE]}_{config[DOMAIN][CONF_STOP]}"
|
|
||||||
config_entry = MockConfigEntry(
|
|
||||||
domain=DOMAIN,
|
|
||||||
data=config[DOMAIN],
|
|
||||||
title=f"{VALID_AGENCY_TITLE} {route_title} {VALID_STOP_TITLE}",
|
|
||||||
unique_id=unique_id,
|
|
||||||
)
|
|
||||||
config_entry.add_to_hass(hass)
|
|
||||||
|
|
||||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert config_entry.state is expected_state
|
|
||||||
|
|
||||||
return config_entry
|
|
||||||
|
|
||||||
|
|
||||||
async def test_predictions(
|
async def test_predictions(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user