Add IMGW-PIB integration (#116468)
* Add sensor platform * Add tests * Fix icons.json * Use entry.runtime_data * Remove validate_input function * Change abort reason to cannot_connect * Remove unnecessary square brackets * Move _attr_attribution outside the constructor * Use native_value property * Use is with ENUMs * Import SOURCE_USER * Change test name * Use freezer.tick * Tests refactoring * Remove test_setup_entry * Test creating entry after error * Add missing async_block_till_done * Fix translation key * Remove coordinator type annotation * Enable strict typing * Assert config entry unique_id --------- Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
This commit is contained in:
parent
53d5960f49
commit
c46be022c8
21 changed files with 877 additions and 0 deletions
|
@ -244,6 +244,7 @@ homeassistant.components.image.*
|
||||||
homeassistant.components.image_processing.*
|
homeassistant.components.image_processing.*
|
||||||
homeassistant.components.image_upload.*
|
homeassistant.components.image_upload.*
|
||||||
homeassistant.components.imap.*
|
homeassistant.components.imap.*
|
||||||
|
homeassistant.components.imgw_pib.*
|
||||||
homeassistant.components.input_button.*
|
homeassistant.components.input_button.*
|
||||||
homeassistant.components.input_select.*
|
homeassistant.components.input_select.*
|
||||||
homeassistant.components.input_text.*
|
homeassistant.components.input_text.*
|
||||||
|
|
|
@ -650,6 +650,8 @@ build.json @home-assistant/supervisor
|
||||||
/tests/components/image_upload/ @home-assistant/core
|
/tests/components/image_upload/ @home-assistant/core
|
||||||
/homeassistant/components/imap/ @jbouwh
|
/homeassistant/components/imap/ @jbouwh
|
||||||
/tests/components/imap/ @jbouwh
|
/tests/components/imap/ @jbouwh
|
||||||
|
/homeassistant/components/imgw_pib/ @bieniu
|
||||||
|
/tests/components/imgw_pib/ @bieniu
|
||||||
/homeassistant/components/improv_ble/ @emontnemery
|
/homeassistant/components/improv_ble/ @emontnemery
|
||||||
/tests/components/improv_ble/ @emontnemery
|
/tests/components/improv_ble/ @emontnemery
|
||||||
/homeassistant/components/incomfort/ @zxdavb
|
/homeassistant/components/incomfort/ @zxdavb
|
||||||
|
|
62
homeassistant/components/imgw_pib/__init__.py
Normal file
62
homeassistant/components/imgw_pib/__init__.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
"""The IMGW-PIB integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from aiohttp import ClientError
|
||||||
|
from imgw_pib import ImgwPib
|
||||||
|
from imgw_pib.exceptions import ApiError
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .const import CONF_STATION_ID
|
||||||
|
from .coordinator import ImgwPibDataUpdateCoordinator
|
||||||
|
|
||||||
|
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ImgwPibConfigEntry = ConfigEntry["ImgwPibData"]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ImgwPibData:
|
||||||
|
"""Data for the IMGW-PIB integration."""
|
||||||
|
|
||||||
|
coordinator: ImgwPibDataUpdateCoordinator
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ImgwPibConfigEntry) -> bool:
|
||||||
|
"""Set up IMGW-PIB from a config entry."""
|
||||||
|
station_id: str = entry.data[CONF_STATION_ID]
|
||||||
|
|
||||||
|
_LOGGER.debug("Using hydrological station ID: %s", station_id)
|
||||||
|
|
||||||
|
client_session = async_get_clientsession(hass)
|
||||||
|
|
||||||
|
try:
|
||||||
|
imgwpib = await ImgwPib.create(
|
||||||
|
client_session, hydrological_station_id=station_id
|
||||||
|
)
|
||||||
|
except (ClientError, TimeoutError, ApiError) as err:
|
||||||
|
raise ConfigEntryNotReady from err
|
||||||
|
|
||||||
|
coordinator = ImgwPibDataUpdateCoordinator(hass, imgwpib, station_id)
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
entry.runtime_data = ImgwPibData(coordinator)
|
||||||
|
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ImgwPibConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
84
homeassistant/components/imgw_pib/config_flow.py
Normal file
84
homeassistant/components/imgw_pib/config_flow.py
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
"""Config flow for IMGW-PIB integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from aiohttp import ClientError
|
||||||
|
from imgw_pib import ImgwPib
|
||||||
|
from imgw_pib.exceptions import ApiError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.selector import (
|
||||||
|
SelectOptionDict,
|
||||||
|
SelectSelector,
|
||||||
|
SelectSelectorConfig,
|
||||||
|
SelectSelectorMode,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import CONF_STATION_ID, DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ImgwPibFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for IMGW-PIB."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle the initial step."""
|
||||||
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
|
client_session = async_get_clientsession(self.hass)
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
station_id = user_input[CONF_STATION_ID]
|
||||||
|
|
||||||
|
await self.async_set_unique_id(station_id, raise_on_progress=False)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
try:
|
||||||
|
imgwpib = await ImgwPib.create(
|
||||||
|
client_session, hydrological_station_id=station_id
|
||||||
|
)
|
||||||
|
hydrological_data = await imgwpib.get_hydrological_data()
|
||||||
|
except (ClientError, TimeoutError, ApiError):
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
title = f"{hydrological_data.river} ({hydrological_data.station})"
|
||||||
|
return self.async_create_entry(title=title, data=user_input)
|
||||||
|
|
||||||
|
try:
|
||||||
|
imgwpib = await ImgwPib.create(client_session)
|
||||||
|
await imgwpib.update_hydrological_stations()
|
||||||
|
except (ClientError, TimeoutError, ApiError):
|
||||||
|
return self.async_abort(reason="cannot_connect")
|
||||||
|
|
||||||
|
options: list[SelectOptionDict] = [
|
||||||
|
SelectOptionDict(value=station_id, label=station_name)
|
||||||
|
for station_id, station_name in imgwpib.hydrological_stations.items()
|
||||||
|
]
|
||||||
|
|
||||||
|
schema: vol.Schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_STATION_ID): SelectSelector(
|
||||||
|
SelectSelectorConfig(
|
||||||
|
options=options,
|
||||||
|
multiple=False,
|
||||||
|
sort=True,
|
||||||
|
mode=SelectSelectorMode.DROPDOWN,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
|
11
homeassistant/components/imgw_pib/const.py
Normal file
11
homeassistant/components/imgw_pib/const.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
"""Constants for the IMGW-PIB integration."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
DOMAIN = "imgw_pib"
|
||||||
|
|
||||||
|
ATTRIBUTION = "Data provided by IMGW-PIB"
|
||||||
|
|
||||||
|
CONF_STATION_ID = "station_id"
|
||||||
|
|
||||||
|
UPDATE_INTERVAL = timedelta(minutes=30)
|
43
homeassistant/components/imgw_pib/coordinator.py
Normal file
43
homeassistant/components/imgw_pib/coordinator.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
"""Data Update Coordinator for IMGW-PIB integration."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from imgw_pib import ApiError, HydrologicalData, ImgwPib
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .const import DOMAIN, UPDATE_INTERVAL
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ImgwPibDataUpdateCoordinator(DataUpdateCoordinator[HydrologicalData]):
|
||||||
|
"""Class to manage fetching IMGW-PIB data API."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
imgwpib: ImgwPib,
|
||||||
|
station_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize."""
|
||||||
|
self.imgwpib = imgwpib
|
||||||
|
self.station_id = station_id
|
||||||
|
self.device_info = DeviceInfo(
|
||||||
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
|
identifiers={(DOMAIN, station_id)},
|
||||||
|
manufacturer="IMGW-PIB",
|
||||||
|
name=f"{imgwpib.hydrological_stations[station_id]}",
|
||||||
|
configuration_url=f"https://hydro.imgw.pl/#/station/hydro/{station_id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL)
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> HydrologicalData:
|
||||||
|
"""Update data via internal method."""
|
||||||
|
try:
|
||||||
|
return await self.imgwpib.get_hydrological_data()
|
||||||
|
except ApiError as err:
|
||||||
|
raise UpdateFailed(err) from err
|
12
homeassistant/components/imgw_pib/icons.json
Normal file
12
homeassistant/components/imgw_pib/icons.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"water_level": {
|
||||||
|
"default": "mdi:waves"
|
||||||
|
},
|
||||||
|
"water_temperature": {
|
||||||
|
"default": "mdi:thermometer-water"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
homeassistant/components/imgw_pib/manifest.json
Normal file
9
homeassistant/components/imgw_pib/manifest.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"domain": "imgw_pib",
|
||||||
|
"name": "IMGW-PIB",
|
||||||
|
"codeowners": ["@bieniu"],
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
||||||
|
"iot_class": "cloud_polling",
|
||||||
|
"requirements": ["imgw_pib==1.0.0"]
|
||||||
|
}
|
97
homeassistant/components/imgw_pib/sensor.py
Normal file
97
homeassistant/components/imgw_pib/sensor.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
"""IMGW-PIB sensor platform."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from imgw_pib.model import HydrologicalData
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
|
SensorStateClass,
|
||||||
|
)
|
||||||
|
from homeassistant.const import UnitOfLength, UnitOfTemperature
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from . import ImgwPibConfigEntry
|
||||||
|
from .const import ATTRIBUTION
|
||||||
|
from .coordinator import ImgwPibDataUpdateCoordinator
|
||||||
|
|
||||||
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class ImgwPibSensorEntityDescription(SensorEntityDescription):
|
||||||
|
"""IMGW-PIB sensor entity description."""
|
||||||
|
|
||||||
|
value: Callable[[HydrologicalData], StateType]
|
||||||
|
|
||||||
|
|
||||||
|
SENSOR_TYPES: tuple[ImgwPibSensorEntityDescription, ...] = (
|
||||||
|
ImgwPibSensorEntityDescription(
|
||||||
|
key="water_level",
|
||||||
|
translation_key="water_level",
|
||||||
|
native_unit_of_measurement=UnitOfLength.CENTIMETERS,
|
||||||
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=0,
|
||||||
|
value=lambda data: data.water_level.value,
|
||||||
|
),
|
||||||
|
ImgwPibSensorEntityDescription(
|
||||||
|
key="water_temperature",
|
||||||
|
translation_key="water_temperature",
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
value=lambda data: data.water_temperature.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ImgwPibConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Add a IMGW-PIB sensor entity from a config_entry."""
|
||||||
|
coordinator = entry.runtime_data.coordinator
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
ImgwPibSensorEntity(coordinator, description)
|
||||||
|
for description in SENSOR_TYPES
|
||||||
|
if getattr(coordinator.data, description.key).value is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ImgwPibSensorEntity(
|
||||||
|
CoordinatorEntity[ImgwPibDataUpdateCoordinator], SensorEntity
|
||||||
|
):
|
||||||
|
"""Define IMGW-PIB sensor entity."""
|
||||||
|
|
||||||
|
_attr_attribution = ATTRIBUTION
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
entity_description: ImgwPibSensorEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: ImgwPibDataUpdateCoordinator,
|
||||||
|
description: ImgwPibSensorEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
|
||||||
|
self._attr_unique_id = f"{coordinator.station_id}_{description.key}"
|
||||||
|
self._attr_device_info = coordinator.device_info
|
||||||
|
self.entity_description = description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> StateType:
|
||||||
|
"""Return the value reported by the sensor."""
|
||||||
|
return self.entity_description.value(self.coordinator.data)
|
29
homeassistant/components/imgw_pib/strings.json
Normal file
29
homeassistant/components/imgw_pib/strings.json
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"station_id": "Hydrological station"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||||
|
"cannot_connect": "Failed to connect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"water_level": {
|
||||||
|
"name": "Water level"
|
||||||
|
},
|
||||||
|
"water_temperature": {
|
||||||
|
"name": "Water temperature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -251,6 +251,7 @@ FLOWS = {
|
||||||
"idasen_desk",
|
"idasen_desk",
|
||||||
"ifttt",
|
"ifttt",
|
||||||
"imap",
|
"imap",
|
||||||
|
"imgw_pib",
|
||||||
"improv_ble",
|
"improv_ble",
|
||||||
"inkbird",
|
"inkbird",
|
||||||
"insteon",
|
"insteon",
|
||||||
|
|
|
@ -2782,6 +2782,12 @@
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "cloud_push"
|
"iot_class": "cloud_push"
|
||||||
},
|
},
|
||||||
|
"imgw_pib": {
|
||||||
|
"name": "IMGW-PIB",
|
||||||
|
"integration_type": "hub",
|
||||||
|
"config_flow": true,
|
||||||
|
"iot_class": "cloud_polling"
|
||||||
|
},
|
||||||
"improv_ble": {
|
"improv_ble": {
|
||||||
"name": "Improv via BLE",
|
"name": "Improv via BLE",
|
||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
|
|
10
mypy.ini
10
mypy.ini
|
@ -2202,6 +2202,16 @@ disallow_untyped_defs = true
|
||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unreachable = true
|
warn_unreachable = true
|
||||||
|
|
||||||
|
[mypy-homeassistant.components.imgw_pib.*]
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unreachable = true
|
||||||
|
|
||||||
[mypy-homeassistant.components.input_button.*]
|
[mypy-homeassistant.components.input_button.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
|
|
|
@ -1136,6 +1136,9 @@ iglo==1.2.7
|
||||||
# homeassistant.components.ihc
|
# homeassistant.components.ihc
|
||||||
ihcsdk==2.8.5
|
ihcsdk==2.8.5
|
||||||
|
|
||||||
|
# homeassistant.components.imgw_pib
|
||||||
|
imgw_pib==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.incomfort
|
# homeassistant.components.incomfort
|
||||||
incomfort-client==0.5.0
|
incomfort-client==0.5.0
|
||||||
|
|
||||||
|
|
|
@ -923,6 +923,9 @@ idasen-ha==2.5.1
|
||||||
# homeassistant.components.network
|
# homeassistant.components.network
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
|
||||||
|
# homeassistant.components.imgw_pib
|
||||||
|
imgw_pib==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.influxdb
|
# homeassistant.components.influxdb
|
||||||
influxdb-client==1.24.0
|
influxdb-client==1.24.0
|
||||||
|
|
||||||
|
|
11
tests/components/imgw_pib/__init__.py
Normal file
11
tests/components/imgw_pib/__init__.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
"""Tests for the IMGW-PIB integration."""
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def init_integration(hass, config_entry: MockConfigEntry) -> MockConfigEntry:
|
||||||
|
"""Set up the IMGW-PIB 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()
|
67
tests/components/imgw_pib/conftest.py
Normal file
67
tests/components/imgw_pib/conftest.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
"""Common fixtures for the IMGW-PIB tests."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
from imgw_pib import HydrologicalData, SensorData
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.imgw_pib.const import DOMAIN
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
HYDROLOGICAL_DATA = HydrologicalData(
|
||||||
|
station="Station Name",
|
||||||
|
river="River Name",
|
||||||
|
station_id="123",
|
||||||
|
water_level=SensorData(name="Water Level", value=526.0),
|
||||||
|
flood_alarm_level=SensorData(name="Flood Alarm Level", value=630.0),
|
||||||
|
flood_warning_level=SensorData(name="Flood Warning Level", value=590.0),
|
||||||
|
water_temperature=SensorData(name="Water Temperature", value=10.8),
|
||||||
|
flood_alarm=False,
|
||||||
|
flood_warning=False,
|
||||||
|
water_level_measurement_date=datetime(2024, 4, 27, 10, 0, tzinfo=UTC),
|
||||||
|
water_temperature_measurement_date=datetime(2024, 4, 27, 10, 10, tzinfo=UTC),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
|
||||||
|
"""Override async_setup_entry."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.imgw_pib.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup_entry:
|
||||||
|
yield mock_setup_entry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_imgw_pib_client() -> Generator[AsyncMock, None, None]:
|
||||||
|
"""Mock a ImgwPib client."""
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.imgw_pib.ImgwPib", autospec=True
|
||||||
|
) as mock_client,
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.imgw_pib.config_flow.ImgwPib",
|
||||||
|
new=mock_client,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
client = mock_client.create.return_value
|
||||||
|
client.get_hydrological_data.return_value = HYDROLOGICAL_DATA
|
||||||
|
client.hydrological_stations = {"123": "River Name (Station Name)"}
|
||||||
|
|
||||||
|
yield client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_config_entry() -> MockConfigEntry:
|
||||||
|
"""Mock a config entry."""
|
||||||
|
return MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="River Name (Station Name)",
|
||||||
|
unique_id="123",
|
||||||
|
data={
|
||||||
|
"station_id": "123",
|
||||||
|
},
|
||||||
|
)
|
221
tests/components/imgw_pib/snapshots/test_sensor.ambr
Normal file
221
tests/components/imgw_pib/snapshots/test_sensor.ambr
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
# serializer version: 1
|
||||||
|
# name: test_sensor[sensor.river_name_station_name_water_level-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.river_name_station_name_water_level',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.DISTANCE: 'distance'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Water level',
|
||||||
|
'platform': 'imgw_pib',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'water_level',
|
||||||
|
'unique_id': '123_water_level',
|
||||||
|
'unit_of_measurement': <UnitOfLength.CENTIMETERS: 'cm'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[sensor.river_name_station_name_water_level-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by IMGW-PIB',
|
||||||
|
'device_class': 'distance',
|
||||||
|
'friendly_name': 'River Name (Station Name) Water level',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfLength.CENTIMETERS: 'cm'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.river_name_station_name_water_level',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '526.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[sensor.river_name_station_name_water_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.river_name_station_name_water_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Water temperature',
|
||||||
|
'platform': 'imgw_pib',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'water_temperature',
|
||||||
|
'unique_id': '123_water_temperature',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[sensor.river_name_station_name_water_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by IMGW-PIB',
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'River Name (Station Name) Water temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.river_name_station_name_water_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '10.8',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[sensor.station_name_water_level-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_name_water_level',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.DISTANCE: 'distance'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Water level',
|
||||||
|
'platform': 'imgw_pib',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'water_level',
|
||||||
|
'unique_id': '123_water_level',
|
||||||
|
'unit_of_measurement': <UnitOfLength.CENTIMETERS: 'cm'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[sensor.station_name_water_level-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by IMGW-PIB',
|
||||||
|
'device_class': 'distance',
|
||||||
|
'friendly_name': 'Station Name Water level',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfLength.CENTIMETERS: 'cm'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_name_water_level',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '526.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[sensor.station_name_water_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.station_name_water_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Water temperature',
|
||||||
|
'platform': 'imgw_pib',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'water_temperature',
|
||||||
|
'unique_id': '123_water_temperature',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[sensor.station_name_water_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'attribution': 'Data provided by IMGW-PIB',
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'Station Name Water temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.station_name_water_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '10.8',
|
||||||
|
})
|
||||||
|
# ---
|
96
tests/components/imgw_pib/test_config_flow.py
Normal file
96
tests/components/imgw_pib/test_config_flow.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
"""Test the IMGW-PIB config flow."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from aiohttp import ClientError
|
||||||
|
from imgw_pib.exceptions import ApiError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.imgw_pib.const import CONF_STATION_ID, DOMAIN
|
||||||
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_entry(
|
||||||
|
hass: HomeAssistant, mock_setup_entry: AsyncMock, mock_imgw_pib_client: 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"],
|
||||||
|
{CONF_STATION_ID: "123"},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "River Name (Station Name)"
|
||||||
|
assert result["data"] == {CONF_STATION_ID: "123"}
|
||||||
|
assert result["context"]["unique_id"] == "123"
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("exc", [ApiError("API Error"), ClientError, TimeoutError])
|
||||||
|
async def test_form_no_station_list(
|
||||||
|
hass: HomeAssistant, exc: Exception, mock_imgw_pib_client: AsyncMock
|
||||||
|
) -> None:
|
||||||
|
"""Test aborting the flow when we cannot get the list of hydrological stations."""
|
||||||
|
mock_imgw_pib_client.update_hydrological_stations.side_effect = exc
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "cannot_connect"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("exc", "base_error"),
|
||||||
|
[
|
||||||
|
(Exception, "unknown"),
|
||||||
|
(ApiError("API Error"), "cannot_connect"),
|
||||||
|
(ClientError, "cannot_connect"),
|
||||||
|
(TimeoutError, "cannot_connect"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_form_with_exceptions(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
exc: Exception,
|
||||||
|
base_error: str,
|
||||||
|
mock_setup_entry: AsyncMock,
|
||||||
|
mock_imgw_pib_client: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test we get the form."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
mock_imgw_pib_client.get_hydrological_data.side_effect = exc
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_STATION_ID: "123"},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["errors"] == {"base": base_error}
|
||||||
|
|
||||||
|
mock_imgw_pib_client.get_hydrological_data.side_effect = None
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_STATION_ID: "123"},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == "River Name (Station Name)"
|
||||||
|
assert result["data"] == {CONF_STATION_ID: "123"}
|
||||||
|
assert result["context"]["unique_id"] == "123"
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
44
tests/components/imgw_pib/test_init.py
Normal file
44
tests/components/imgw_pib/test_init.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
"""Test init of IMGW-PIB integration."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from imgw_pib import ApiError
|
||||||
|
|
||||||
|
from homeassistant.components.imgw_pib.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from . import init_integration
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_not_ready(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_imgw_pib_client: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test for setup failure if the connection to the service fails."""
|
||||||
|
mock_imgw_pib_client.get_hydrological_data.side_effect = ApiError("API Error")
|
||||||
|
|
||||||
|
await init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_unload_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_imgw_pib_client: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test successful unload of entry."""
|
||||||
|
await init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||||
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||||
|
assert not hass.data.get(DOMAIN)
|
65
tests/components/imgw_pib/test_sensor.py
Normal file
65
tests/components/imgw_pib/test_sensor.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
"""Test the IMGW-PIB sensor platform."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from imgw_pib import ApiError
|
||||||
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.components.imgw_pib.const import UPDATE_INTERVAL
|
||||||
|
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from . import init_integration
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||||
|
|
||||||
|
ENTITY_ID = "sensor.river_name_station_name_water_level"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
mock_imgw_pib_client: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test states of the sensor."""
|
||||||
|
with patch("homeassistant.components.imgw_pib.PLATFORMS", [Platform.SENSOR]):
|
||||||
|
await init_integration(hass, mock_config_entry)
|
||||||
|
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_availability(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
mock_imgw_pib_client: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure that we mark the entities unavailable correctly when service is offline."""
|
||||||
|
await init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
assert state.state == "526.0"
|
||||||
|
|
||||||
|
mock_imgw_pib_client.get_hydrological_data.side_effect = ApiError("API Error")
|
||||||
|
freezer.tick(UPDATE_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
mock_imgw_pib_client.get_hydrological_data.side_effect = None
|
||||||
|
freezer.tick(UPDATE_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(ENTITY_ID)
|
||||||
|
assert state
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
assert state.state == "526.0"
|
Loading…
Add table
Add a link
Reference in a new issue