mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 10:47:10 +00:00
Add Israel rail integration (#121418)
* Add Israel Rail integration * israel_rail tests * israel_rail tests * 1. use entry.runtime 2. DataConnection - data class 3. remove unique id from coordinator 4. use EntityDescription * add a list of stations in user form * 1. extend ConfigEntry 2. remove unused pop 3. use IsraelRailSensorEntityDescription to have only one kind of Sensor 4. add test for already configured 5. use snapshot in test * change user step description * 1. ConfigEntry[IsraelRailDataUpdateCoordinator] 2. remove redundant attributes 3. use snapshot_platform helper * remove attr * remove attr * move test to test_init.py * Fix * Fix * Fix * Fix * fix timezone * fix * fix --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
ea5eb0f8f2
commit
56b6747bc0
@ -707,6 +707,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/isal/ @bdraco
|
/tests/components/isal/ @bdraco
|
||||||
/homeassistant/components/islamic_prayer_times/ @engrbm87 @cpfair
|
/homeassistant/components/islamic_prayer_times/ @engrbm87 @cpfair
|
||||||
/tests/components/islamic_prayer_times/ @engrbm87 @cpfair
|
/tests/components/islamic_prayer_times/ @engrbm87 @cpfair
|
||||||
|
/homeassistant/components/israel_rail/ @shaiu
|
||||||
|
/tests/components/israel_rail/ @shaiu
|
||||||
/homeassistant/components/iss/ @DurgNomis-drol
|
/homeassistant/components/iss/ @DurgNomis-drol
|
||||||
/tests/components/iss/ @DurgNomis-drol
|
/tests/components/iss/ @DurgNomis-drol
|
||||||
/homeassistant/components/ista_ecotrend/ @tr4nt0r
|
/homeassistant/components/ista_ecotrend/ @tr4nt0r
|
||||||
|
58
homeassistant/components/israel_rail/__init__.py
Normal file
58
homeassistant/components/israel_rail/__init__.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"""The Israel Rail component."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from israelrailapi import TrainSchedule
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
|
||||||
|
from .const import CONF_DESTINATION, CONF_START, DOMAIN
|
||||||
|
from .coordinator import IsraelRailDataUpdateCoordinator
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
|
type IsraelRailConfigEntry = ConfigEntry[IsraelRailDataUpdateCoordinator]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: IsraelRailConfigEntry) -> bool:
|
||||||
|
"""Set up Israel rail from a config entry."""
|
||||||
|
config = entry.data
|
||||||
|
|
||||||
|
start = config[CONF_START]
|
||||||
|
destination = config[CONF_DESTINATION]
|
||||||
|
|
||||||
|
train_schedule = TrainSchedule()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await hass.async_add_executor_job(train_schedule.query, start, destination)
|
||||||
|
except Exception as e:
|
||||||
|
raise ConfigEntryNotReady(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="request_timeout",
|
||||||
|
translation_placeholders={
|
||||||
|
"config_title": entry.title,
|
||||||
|
"error": str(e),
|
||||||
|
},
|
||||||
|
) from e
|
||||||
|
|
||||||
|
israel_rail_coordinator = IsraelRailDataUpdateCoordinator(
|
||||||
|
hass, train_schedule, start, destination
|
||||||
|
)
|
||||||
|
await israel_rail_coordinator.async_config_entry_first_refresh()
|
||||||
|
entry.runtime_data = israel_rail_coordinator
|
||||||
|
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: IsraelRailConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
61
homeassistant/components/israel_rail/config_flow.py
Normal file
61
homeassistant/components/israel_rail/config_flow.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"""Config flow for israel rail."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from israelrailapi import TrainSchedule
|
||||||
|
from israelrailapi.stations import STATIONS
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
|
|
||||||
|
from .const import CONF_DESTINATION, CONF_START, DOMAIN
|
||||||
|
|
||||||
|
STATIONS_NAMES = [station["Heb"] for station in STATIONS.values()]
|
||||||
|
|
||||||
|
DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_START): vol.In(STATIONS_NAMES),
|
||||||
|
vol.Required(CONF_DESTINATION): vol.In(STATIONS_NAMES),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IsraelRailConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Israel rail config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Async user step to set up the connection."""
|
||||||
|
errors = {}
|
||||||
|
if user_input:
|
||||||
|
train_schedule = TrainSchedule()
|
||||||
|
try:
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
train_schedule.query,
|
||||||
|
user_input[CONF_START],
|
||||||
|
user_input[CONF_DESTINATION],
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.exception("Unknown error")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
if not errors:
|
||||||
|
unique_id = f"{user_input[CONF_START]} {user_input[CONF_DESTINATION]}"
|
||||||
|
await self.async_set_unique_id(unique_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=unique_id,
|
||||||
|
data=user_input,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=DATA_SCHEMA,
|
||||||
|
errors=errors,
|
||||||
|
)
|
17
homeassistant/components/israel_rail/const.py
Normal file
17
homeassistant/components/israel_rail/const.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""Constants for the israel rail integration."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
DOMAIN = "israel_rail"
|
||||||
|
|
||||||
|
CONF_START: Final = "from"
|
||||||
|
CONF_DESTINATION: Final = "to"
|
||||||
|
|
||||||
|
DEFAULT_NAME = "Next Destination"
|
||||||
|
|
||||||
|
DEPARTURES_COUNT = 3
|
||||||
|
|
||||||
|
DEFAULT_SCAN_INTERVAL = timedelta(seconds=90)
|
||||||
|
|
||||||
|
ATTRIBUTION = "Data provided by Israel rail."
|
113
homeassistant/components/israel_rail/coordinator.py
Normal file
113
homeassistant/components/israel_rail/coordinator.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
"""DataUpdateCoordinator for the israel rail integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from israelrailapi import TrainSchedule
|
||||||
|
from israelrailapi.api import TrainRoute
|
||||||
|
from israelrailapi.train_station import station_name_to_id
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
|
from .const import DEFAULT_SCAN_INTERVAL, DEPARTURES_COUNT, DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DataConnection:
|
||||||
|
"""A connection data class."""
|
||||||
|
|
||||||
|
departure: datetime | None
|
||||||
|
duration: int | None
|
||||||
|
platform: str
|
||||||
|
remaining_time: str
|
||||||
|
start: str
|
||||||
|
destination: str
|
||||||
|
train_number: str
|
||||||
|
transfers: int
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_duration_in_seconds(start_time: str, end_time: str) -> int | None:
|
||||||
|
"""Transform and calculate the duration from start and end time into seconds."""
|
||||||
|
end_time_date = dt_util.parse_datetime(end_time)
|
||||||
|
start_time_date = dt_util.parse_datetime(start_time)
|
||||||
|
if not end_time_date or not start_time_date:
|
||||||
|
return None
|
||||||
|
return (end_time_date - start_time_date).seconds
|
||||||
|
|
||||||
|
|
||||||
|
def departure_time(train_route: TrainRoute) -> datetime | None:
|
||||||
|
"""Get departure time."""
|
||||||
|
start_datetime = dt_util.parse_datetime(train_route.start_time)
|
||||||
|
return start_datetime.astimezone() if start_datetime else None
|
||||||
|
|
||||||
|
|
||||||
|
def remaining_time(departure) -> timedelta | None:
|
||||||
|
"""Calculate the remaining time for the departure."""
|
||||||
|
departure_datetime = dt_util.parse_datetime(departure)
|
||||||
|
|
||||||
|
if departure_datetime:
|
||||||
|
return dt_util.as_local(departure_datetime) - dt_util.as_local(dt_util.utcnow())
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class IsraelRailDataUpdateCoordinator(DataUpdateCoordinator[list[DataConnection]]):
|
||||||
|
"""A IsraelRail Data Update Coordinator."""
|
||||||
|
|
||||||
|
config_entry: ConfigEntry
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
train_schedule: TrainSchedule,
|
||||||
|
start: str,
|
||||||
|
destination: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the IsraelRail data coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name=DOMAIN,
|
||||||
|
update_interval=DEFAULT_SCAN_INTERVAL,
|
||||||
|
)
|
||||||
|
self._train_schedule = train_schedule
|
||||||
|
self._start = start
|
||||||
|
self._destination = destination
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> list[DataConnection]:
|
||||||
|
try:
|
||||||
|
train_routes = await self.hass.async_add_executor_job(
|
||||||
|
self._train_schedule.query,
|
||||||
|
self._start,
|
||||||
|
self._destination,
|
||||||
|
datetime.now().strftime("%Y-%m-%d"),
|
||||||
|
datetime.now().strftime("%H:%M"),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise UpdateFailed(
|
||||||
|
"Unable to connect and retrieve data from israelrail api",
|
||||||
|
) from e
|
||||||
|
|
||||||
|
return [
|
||||||
|
DataConnection(
|
||||||
|
departure=departure_time(train_routes[i]),
|
||||||
|
train_number=train_routes[i].trains[0].data["trainNumber"],
|
||||||
|
platform=train_routes[i].trains[0].platform,
|
||||||
|
transfers=len(train_routes[i].trains) - 1,
|
||||||
|
duration=calculate_duration_in_seconds(
|
||||||
|
train_routes[i].start_time, train_routes[i].end_time
|
||||||
|
),
|
||||||
|
start=station_name_to_id(train_routes[i].trains[0].src),
|
||||||
|
destination=station_name_to_id(train_routes[i].trains[-1].dst),
|
||||||
|
remaining_time=str(remaining_time(train_routes[i].trains[0].departure)),
|
||||||
|
)
|
||||||
|
for i in range(DEPARTURES_COUNT)
|
||||||
|
if len(train_routes) > i and train_routes[i] is not None
|
||||||
|
]
|
27
homeassistant/components/israel_rail/icons.json
Normal file
27
homeassistant/components/israel_rail/icons.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"departure0": {
|
||||||
|
"default": "mdi:bus-clock"
|
||||||
|
},
|
||||||
|
"departure1": {
|
||||||
|
"default": "mdi:bus-clock"
|
||||||
|
},
|
||||||
|
"departure2": {
|
||||||
|
"default": "mdi:bus-clock"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"default": "mdi:timeline-clock"
|
||||||
|
},
|
||||||
|
"transfers": {
|
||||||
|
"default": "mdi:transit-transfer"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"default": "mdi:bus-stop-uncovered"
|
||||||
|
},
|
||||||
|
"train_number": {
|
||||||
|
"default": "mdi:train"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
homeassistant/components/israel_rail/manifest.json
Normal file
10
homeassistant/components/israel_rail/manifest.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"domain": "israel_rail",
|
||||||
|
"name": "Israel Railways",
|
||||||
|
"codeowners": ["@shaiu"],
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/israel_rail",
|
||||||
|
"iot_class": "cloud_polling",
|
||||||
|
"loggers": ["israelrailapi"],
|
||||||
|
"requirements": ["israel-rail-api==0.1.2"]
|
||||||
|
}
|
125
homeassistant/components/israel_rail/sensor.py
Normal file
125
homeassistant/components/israel_rail/sensor.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
"""Support for israel rail."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
|
)
|
||||||
|
from homeassistant.const import UnitOfTime
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from . import IsraelRailConfigEntry
|
||||||
|
from .const import ATTRIBUTION, DEPARTURES_COUNT, DOMAIN
|
||||||
|
from .coordinator import DataConnection, IsraelRailDataUpdateCoordinator
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(kw_only=True, frozen=True)
|
||||||
|
class IsraelRailSensorEntityDescription(SensorEntityDescription):
|
||||||
|
"""Describes israel rail sensor entity."""
|
||||||
|
|
||||||
|
value_fn: Callable[[DataConnection], StateType | datetime]
|
||||||
|
|
||||||
|
index: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
DEPARTURE_SENSORS: tuple[IsraelRailSensorEntityDescription, ...] = (
|
||||||
|
*[
|
||||||
|
IsraelRailSensorEntityDescription(
|
||||||
|
key=f"departure{i or ''}",
|
||||||
|
translation_key=f"departure{i}",
|
||||||
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
|
value_fn=lambda data_connection: data_connection.departure,
|
||||||
|
index=i,
|
||||||
|
)
|
||||||
|
for i in range(DEPARTURES_COUNT)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
SENSORS: tuple[IsraelRailSensorEntityDescription, ...] = (
|
||||||
|
IsraelRailSensorEntityDescription(
|
||||||
|
key="duration",
|
||||||
|
device_class=SensorDeviceClass.DURATION,
|
||||||
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||||
|
value_fn=lambda data_connection: data_connection.duration,
|
||||||
|
),
|
||||||
|
IsraelRailSensorEntityDescription(
|
||||||
|
key="platform",
|
||||||
|
translation_key="platform",
|
||||||
|
value_fn=lambda data_connection: data_connection.platform,
|
||||||
|
),
|
||||||
|
IsraelRailSensorEntityDescription(
|
||||||
|
key="transfers",
|
||||||
|
translation_key="transfers",
|
||||||
|
value_fn=lambda data_connection: data_connection.transfers,
|
||||||
|
),
|
||||||
|
IsraelRailSensorEntityDescription(
|
||||||
|
key="train_number",
|
||||||
|
translation_key="train_number",
|
||||||
|
value_fn=lambda data_connection: data_connection.train_number,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: IsraelRailConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up the sensor from a config entry created in the integrations UI."""
|
||||||
|
coordinator = config_entry.runtime_data
|
||||||
|
|
||||||
|
unique_id = config_entry.unique_id
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert unique_id
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
IsraelRailEntitySensor(coordinator, description, unique_id)
|
||||||
|
for description in (*DEPARTURE_SENSORS, *SENSORS)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IsraelRailEntitySensor(
|
||||||
|
CoordinatorEntity[IsraelRailDataUpdateCoordinator], SensorEntity
|
||||||
|
):
|
||||||
|
"""Define a Israel Rail sensor."""
|
||||||
|
|
||||||
|
entity_description: IsraelRailSensorEntityDescription
|
||||||
|
_attr_attribution = ATTRIBUTION
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: IsraelRailDataUpdateCoordinator,
|
||||||
|
entity_description: IsraelRailSensorEntityDescription,
|
||||||
|
unique_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self.entity_description = entity_description
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, unique_id)},
|
||||||
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
|
)
|
||||||
|
self._attr_unique_id = f"{unique_id}_{entity_description.key}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> StateType | datetime:
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self.entity_description.value_fn(
|
||||||
|
self.coordinator.data[self.entity_description.index]
|
||||||
|
)
|
42
homeassistant/components/israel_rail/strings.json
Normal file
42
homeassistant/components/israel_rail/strings.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"error": {
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"from": "Start station",
|
||||||
|
"to": "End station"
|
||||||
|
},
|
||||||
|
"description": "Provide start and end station for your connection from the provided list",
|
||||||
|
"title": "Israel Rail"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"departure0": {
|
||||||
|
"name": "Departure"
|
||||||
|
},
|
||||||
|
"departure1": {
|
||||||
|
"name": "Departure +1"
|
||||||
|
},
|
||||||
|
"departure2": {
|
||||||
|
"name": "Departure +2"
|
||||||
|
},
|
||||||
|
"transfers": {
|
||||||
|
"name": "Transfers"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"name": "Platform"
|
||||||
|
},
|
||||||
|
"train_number": {
|
||||||
|
"name": "Train number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -273,6 +273,7 @@ FLOWS = {
|
|||||||
"ipp",
|
"ipp",
|
||||||
"iqvia",
|
"iqvia",
|
||||||
"islamic_prayer_times",
|
"islamic_prayer_times",
|
||||||
|
"israel_rail",
|
||||||
"iss",
|
"iss",
|
||||||
"ista_ecotrend",
|
"ista_ecotrend",
|
||||||
"isy994",
|
"isy994",
|
||||||
|
@ -2909,6 +2909,12 @@
|
|||||||
"integration_type": "virtual",
|
"integration_type": "virtual",
|
||||||
"supported_by": "motion_blinds"
|
"supported_by": "motion_blinds"
|
||||||
},
|
},
|
||||||
|
"israel_rail": {
|
||||||
|
"name": "Israel Railways",
|
||||||
|
"integration_type": "hub",
|
||||||
|
"config_flow": true,
|
||||||
|
"iot_class": "cloud_polling"
|
||||||
|
},
|
||||||
"iss": {
|
"iss": {
|
||||||
"name": "International Space Station (ISS)",
|
"name": "International Space Station (ISS)",
|
||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
|
@ -1184,6 +1184,9 @@ isal==1.6.1
|
|||||||
# homeassistant.components.gogogate2
|
# homeassistant.components.gogogate2
|
||||||
ismartgate==5.0.1
|
ismartgate==5.0.1
|
||||||
|
|
||||||
|
# homeassistant.components.israel_rail
|
||||||
|
israel-rail-api==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.abode
|
# homeassistant.components.abode
|
||||||
jaraco.abode==5.2.1
|
jaraco.abode==5.2.1
|
||||||
|
|
||||||
|
@ -971,6 +971,9 @@ isal==1.6.1
|
|||||||
# homeassistant.components.gogogate2
|
# homeassistant.components.gogogate2
|
||||||
ismartgate==5.0.1
|
ismartgate==5.0.1
|
||||||
|
|
||||||
|
# homeassistant.components.israel_rail
|
||||||
|
israel-rail-api==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.abode
|
# homeassistant.components.abode
|
||||||
jaraco.abode==5.2.1
|
jaraco.abode==5.2.1
|
||||||
|
|
||||||
|
28
tests/components/israel_rail/__init__.py
Normal file
28
tests/components/israel_rail/__init__.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""Tests for the israel_rail component."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
|
||||||
|
from homeassistant.components.israel_rail.const import DEFAULT_SCAN_INTERVAL
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
async def init_integration(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Set up the israel rail integration in Home Assistant."""
|
||||||
|
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def goto_future(hass: HomeAssistant, freezer: FrozenDateTimeFactory):
|
||||||
|
"""Move to future."""
|
||||||
|
freezer.tick(DEFAULT_SCAN_INTERVAL + timedelta(minutes=1))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
185
tests/components/israel_rail/conftest.py
Normal file
185
tests/components/israel_rail/conftest.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
"""Configuration for Israel rail tests."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from unittest.mock import AsyncMock, patch
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
from israelrailapi.api import TrainRoute
|
||||||
|
import pytest
|
||||||
|
from typing_extensions import Generator
|
||||||
|
|
||||||
|
from homeassistant.components.israel_rail import CONF_DESTINATION, CONF_START, DOMAIN
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
VALID_CONFIG = {
|
||||||
|
CONF_START: "באר יעקב",
|
||||||
|
CONF_DESTINATION: "אשקלון",
|
||||||
|
}
|
||||||
|
|
||||||
|
SOURCE_DEST = "באר יעקב אשקלון"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||||
|
"""Override async_setup_entry."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.israel_rail.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup_entry:
|
||||||
|
yield mock_setup_entry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_config_entry() -> MockConfigEntry:
|
||||||
|
"""Return the default mocked config entry."""
|
||||||
|
return MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data=VALID_CONFIG,
|
||||||
|
unique_id=SOURCE_DEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_israelrail() -> AsyncMock:
|
||||||
|
"""Build a fixture for the Israel rail API."""
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.israel_rail.TrainSchedule",
|
||||||
|
autospec=True,
|
||||||
|
) as mock_client,
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.israel_rail.config_flow.TrainSchedule",
|
||||||
|
new=mock_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
client = mock_client.return_value
|
||||||
|
client.query.return_value = TRAINS
|
||||||
|
|
||||||
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
def get_time(hour: int, minute: int) -> str:
|
||||||
|
"""Return a time in isoformat."""
|
||||||
|
return datetime(2021, 10, 10, hour, minute, 10, tzinfo=ZoneInfo("UTC")).isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def get_train_route(
|
||||||
|
train_number: str = "1234",
|
||||||
|
departure_time: str = "2021-10-10T10:10:10",
|
||||||
|
arrival_time: str = "2021-10-10T10:10:10",
|
||||||
|
origin_platform: str = "1",
|
||||||
|
dest_platform: str = "2",
|
||||||
|
origin_station: str = "3500",
|
||||||
|
destination_station: str = "3700",
|
||||||
|
) -> TrainRoute:
|
||||||
|
"""Build a TrainRoute of the israelrail API."""
|
||||||
|
return TrainRoute(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"orignStation": origin_station,
|
||||||
|
"destinationStation": destination_station,
|
||||||
|
"departureTime": departure_time,
|
||||||
|
"arrivalTime": arrival_time,
|
||||||
|
"originPlatform": origin_platform,
|
||||||
|
"destPlatform": dest_platform,
|
||||||
|
"trainNumber": train_number,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
TRAINS = [
|
||||||
|
get_train_route(
|
||||||
|
train_number="1234",
|
||||||
|
departure_time=get_time(10, 10),
|
||||||
|
arrival_time=get_time(10, 30),
|
||||||
|
origin_platform="1",
|
||||||
|
dest_platform="2",
|
||||||
|
origin_station="3500",
|
||||||
|
destination_station="3700",
|
||||||
|
),
|
||||||
|
get_train_route(
|
||||||
|
train_number="1235",
|
||||||
|
departure_time=get_time(10, 20),
|
||||||
|
arrival_time=get_time(10, 40),
|
||||||
|
origin_platform="1",
|
||||||
|
dest_platform="2",
|
||||||
|
origin_station="3500",
|
||||||
|
destination_station="3700",
|
||||||
|
),
|
||||||
|
get_train_route(
|
||||||
|
train_number="1236",
|
||||||
|
departure_time=get_time(10, 30),
|
||||||
|
arrival_time=get_time(10, 50),
|
||||||
|
origin_platform="1",
|
||||||
|
dest_platform="2",
|
||||||
|
origin_station="3500",
|
||||||
|
destination_station="3700",
|
||||||
|
),
|
||||||
|
get_train_route(
|
||||||
|
train_number="1237",
|
||||||
|
departure_time=get_time(10, 40),
|
||||||
|
arrival_time=get_time(11, 00),
|
||||||
|
origin_platform="1",
|
||||||
|
dest_platform="2",
|
||||||
|
origin_station="3500",
|
||||||
|
destination_station="3700",
|
||||||
|
),
|
||||||
|
get_train_route(
|
||||||
|
train_number="1238",
|
||||||
|
departure_time=get_time(10, 50),
|
||||||
|
arrival_time=get_time(11, 10),
|
||||||
|
origin_platform="1",
|
||||||
|
dest_platform="2",
|
||||||
|
origin_station="3500",
|
||||||
|
destination_station="3700",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
TRAINS_WRONG_FORMAT = [
|
||||||
|
get_train_route(
|
||||||
|
train_number="1234",
|
||||||
|
departure_time="2021-10-1010:10:10",
|
||||||
|
arrival_time="2021-10-10T10:30:10",
|
||||||
|
origin_platform="1",
|
||||||
|
dest_platform="2",
|
||||||
|
origin_station="3500",
|
||||||
|
destination_station="3700",
|
||||||
|
),
|
||||||
|
get_train_route(
|
||||||
|
train_number="1235",
|
||||||
|
departure_time="2021-10-1010:20:10",
|
||||||
|
arrival_time="2021-10-10T10:40:10",
|
||||||
|
origin_platform="1",
|
||||||
|
dest_platform="2",
|
||||||
|
origin_station="3500",
|
||||||
|
destination_station="3700",
|
||||||
|
),
|
||||||
|
get_train_route(
|
||||||
|
train_number="1236",
|
||||||
|
departure_time="2021-10-1010:30:10",
|
||||||
|
arrival_time="2021-10-10T10:50:10",
|
||||||
|
origin_platform="1",
|
||||||
|
dest_platform="2",
|
||||||
|
origin_station="3500",
|
||||||
|
destination_station="3700",
|
||||||
|
),
|
||||||
|
get_train_route(
|
||||||
|
train_number="1237",
|
||||||
|
departure_time="2021-10-1010:40:10",
|
||||||
|
arrival_time="2021-10-10T11:00:10",
|
||||||
|
origin_platform="1",
|
||||||
|
dest_platform="2",
|
||||||
|
origin_station="3500",
|
||||||
|
destination_station="3700",
|
||||||
|
),
|
||||||
|
get_train_route(
|
||||||
|
train_number="1238",
|
||||||
|
departure_time="2021-10-1010:50:10",
|
||||||
|
arrival_time="2021-10-10T11:10:10",
|
||||||
|
origin_platform="1",
|
||||||
|
dest_platform="2",
|
||||||
|
origin_station="3500",
|
||||||
|
destination_station="3700",
|
||||||
|
),
|
||||||
|
]
|
335
tests/components/israel_rail/snapshots/test_sensor.ambr
Normal file
335
tests/components/israel_rail/snapshots/test_sensor.ambr
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_valid_config[sensor.mock_title_departure-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.mock_title_departure',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Departure',
|
||||||
|
'platform': 'israel_rail',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'departure0',
|
||||||
|
'unique_id': 'באר יעקב אשקלון_departure',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_departure-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by Israel rail.',
|
||||||
|
'device_class': 'timestamp',
|
||||||
|
'friendly_name': 'Mock Title Departure',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.mock_title_departure',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '2021-10-10T10:10:10+00:00',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_departure_1-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.mock_title_departure_1',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Departure +1',
|
||||||
|
'platform': 'israel_rail',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'departure1',
|
||||||
|
'unique_id': 'באר יעקב אשקלון_departure1',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_departure_1-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by Israel rail.',
|
||||||
|
'device_class': 'timestamp',
|
||||||
|
'friendly_name': 'Mock Title Departure +1',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.mock_title_departure_1',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '2021-10-10T10:20:10+00:00',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_departure_2-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.mock_title_departure_2',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Departure +2',
|
||||||
|
'platform': 'israel_rail',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'departure2',
|
||||||
|
'unique_id': 'באר יעקב אשקלון_departure2',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_departure_2-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by Israel rail.',
|
||||||
|
'device_class': 'timestamp',
|
||||||
|
'friendly_name': 'Mock Title Departure +2',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.mock_title_departure_2',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '2021-10-10T10:30:10+00:00',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_duration-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.mock_title_duration',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Duration',
|
||||||
|
'platform': 'israel_rail',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': 'באר יעקב אשקלון_duration',
|
||||||
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_duration-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by Israel rail.',
|
||||||
|
'device_class': 'duration',
|
||||||
|
'friendly_name': 'Mock Title Duration',
|
||||||
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.mock_title_duration',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '1200',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_platform-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.mock_title_platform',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Platform',
|
||||||
|
'platform': 'israel_rail',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'platform',
|
||||||
|
'unique_id': 'באר יעקב אשקלון_platform',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_platform-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by Israel rail.',
|
||||||
|
'friendly_name': 'Mock Title Platform',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.mock_title_platform',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '1',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_train_number-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.mock_title_train_number',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Train number',
|
||||||
|
'platform': 'israel_rail',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'train_number',
|
||||||
|
'unique_id': 'באר יעקב אשקלון_train_number',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_train_number-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by Israel rail.',
|
||||||
|
'friendly_name': 'Mock Title Train number',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.mock_title_train_number',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '1234',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_transfers-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.mock_title_transfers',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Transfers',
|
||||||
|
'platform': 'israel_rail',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'transfers',
|
||||||
|
'unique_id': 'באר יעקב אשקלון_transfers',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_valid_config[sensor.mock_title_transfers-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by Israel rail.',
|
||||||
|
'friendly_name': 'Mock Title Transfers',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.mock_title_transfers',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '0',
|
||||||
|
})
|
||||||
|
# ---
|
87
tests/components/israel_rail/test_config_flow.py
Normal file
87
tests/components/israel_rail/test_config_flow.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
"""Define tests for the israel rail config flow."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from homeassistant.components.israel_rail import CONF_DESTINATION, CONF_START, DOMAIN
|
||||||
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
|
from .conftest import VALID_CONFIG
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_entry(
|
||||||
|
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_israelrail: AsyncMock
|
||||||
|
) -> None:
|
||||||
|
"""Test that the user step works."""
|
||||||
|
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"],
|
||||||
|
VALID_CONFIG,
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "באר יעקב אשקלון"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_START: "באר יעקב",
|
||||||
|
CONF_DESTINATION: "אשקלון",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_fails(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_israelrail: AsyncMock,
|
||||||
|
mock_setup_entry: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test that the user step fails."""
|
||||||
|
mock_israelrail.query.side_effect = Exception("error")
|
||||||
|
failed_result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_USER},
|
||||||
|
data=VALID_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert failed_result["errors"] == {"base": "unknown"}
|
||||||
|
assert failed_result["type"] is FlowResultType.FORM
|
||||||
|
|
||||||
|
mock_israelrail.query.side_effect = None
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
failed_result["flow_id"],
|
||||||
|
VALID_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "באר יעקב אשקלון"
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_START: "באר יעקב",
|
||||||
|
CONF_DESTINATION: "אשקלון",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_already_configured(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_israelrail: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_setup_entry: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test that the user step fails when the entry is already configured."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
result_aborted = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
VALID_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result_aborted["type"] is FlowResultType.ABORT
|
||||||
|
assert result_aborted["reason"] == "already_configured"
|
22
tests/components/israel_rail/test_init.py
Normal file
22
tests/components/israel_rail/test_init.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""Test init of israel_rail integration."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from . import init_integration
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_invalid_config(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_israelrail: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure nothing is created when config is wrong."""
|
||||||
|
mock_israelrail.query.side_effect = Exception("error")
|
||||||
|
await init_integration(hass, mock_config_entry)
|
||||||
|
assert not hass.states.async_entity_ids("sensor")
|
||||||
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
84
tests/components/israel_rail/test_sensor.py
Normal file
84
tests/components/israel_rail/test_sensor.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
"""Tests for the israel_rail sensor."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.const import STATE_UNAVAILABLE
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from . import goto_future, init_integration
|
||||||
|
from .conftest import TRAINS, TRAINS_WRONG_FORMAT, get_time
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, snapshot_platform
|
||||||
|
|
||||||
|
|
||||||
|
async def test_valid_config(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_israelrail: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure everything starts correctly."""
|
||||||
|
await init_integration(hass, mock_config_entry)
|
||||||
|
assert len(hass.states.async_entity_ids()) == 7
|
||||||
|
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_train(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
mock_israelrail: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure the train data is updated."""
|
||||||
|
await init_integration(hass, mock_config_entry)
|
||||||
|
assert len(hass.states.async_entity_ids()) == 7
|
||||||
|
departure_sensor = hass.states.get("sensor.mock_title_departure")
|
||||||
|
expected_time = get_time(10, 10)
|
||||||
|
assert departure_sensor.state == expected_time
|
||||||
|
|
||||||
|
mock_israelrail.query.return_value = TRAINS[1:]
|
||||||
|
|
||||||
|
await goto_future(hass, freezer)
|
||||||
|
|
||||||
|
assert len(hass.states.async_entity_ids()) == 7
|
||||||
|
departure_sensor = hass.states.get("sensor.mock_title_departure")
|
||||||
|
expected_time = get_time(10, 20)
|
||||||
|
assert departure_sensor.state == expected_time
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_duration_wrong_date_format(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_israelrail: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure the duration is not set when there is no departure time."""
|
||||||
|
mock_israelrail.query.return_value = TRAINS_WRONG_FORMAT
|
||||||
|
await init_integration(hass, mock_config_entry)
|
||||||
|
assert len(hass.states.async_entity_ids()) == 7
|
||||||
|
departure_sensor = hass.states.get("sensor.mock_title_train_number")
|
||||||
|
assert departure_sensor.state == "1234"
|
||||||
|
duration_sensor = hass.states.get("sensor.mock_title_duration")
|
||||||
|
assert duration_sensor.state == "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_fail_query(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
mock_israelrail: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure the integration handles query failures."""
|
||||||
|
await init_integration(hass, mock_config_entry)
|
||||||
|
assert len(hass.states.async_entity_ids()) == 7
|
||||||
|
mock_israelrail.query.side_effect = Exception("error")
|
||||||
|
await goto_future(hass, freezer)
|
||||||
|
assert len(hass.states.async_entity_ids()) == 7
|
||||||
|
departure_sensor = hass.states.get("sensor.mock_title_departure")
|
||||||
|
assert departure_sensor.state == STATE_UNAVAILABLE
|
Loading…
x
Reference in New Issue
Block a user