Suez water: fetch historical data in statistics (#131166)

* Suez water: fetch historical data in statistics

* test review

* wip: fix few things

* Python is smarter than me

* use snapshots for statistics and add hard limit for historical stats

* refactor refresh + handle missing price

* No more auth error raised

* fix after rebase

* Review - much cleaner <3

* fix changes

* test without snapshots

* fix imports
This commit is contained in:
jb101010-2 2025-05-20 16:22:35 +02:00 committed by GitHub
parent 4160ed190c
commit 4737091722
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 682 additions and 51 deletions

View File

@ -1,18 +1,35 @@
"""Suez water update coordinator."""
from dataclasses import dataclass
from datetime import date
from datetime import date, datetime
import logging
from pysuez import PySuezError, SuezClient
from pysuez import PySuezError, SuezClient, TelemetryMeasure
from homeassistant.components.recorder import get_instance
from homeassistant.components.recorder.models import StatisticData, StatisticMetaData
from homeassistant.components.recorder.statistics import (
StatisticMeanType,
StatisticsRow,
async_add_external_statistics,
get_last_statistics,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import _LOGGER, HomeAssistant
from homeassistant.const import (
CONF_PASSWORD,
CONF_USERNAME,
CURRENCY_EURO,
UnitOfVolume,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
import homeassistant.util.dt as dt_util
from .const import CONF_COUNTER_ID, DATA_REFRESH_INTERVAL, DOMAIN
_LOGGER = logging.getLogger(__name__)
@dataclass
class SuezWaterAggregatedAttributes:
@ -32,7 +49,7 @@ class SuezWaterData:
aggregated_value: float
aggregated_attr: SuezWaterAggregatedAttributes
price: float
price: float | None
type SuezWaterConfigEntry = ConfigEntry[SuezWaterCoordinator]
@ -54,6 +71,11 @@ class SuezWaterCoordinator(DataUpdateCoordinator[SuezWaterData]):
always_update=True,
config_entry=config_entry,
)
self._counter_id = self.config_entry.data[CONF_COUNTER_ID]
self._cost_statistic_id = f"{DOMAIN}:{self._counter_id}_water_cost_statistics"
self._water_statistic_id = (
f"{DOMAIN}:{self._counter_id}_water_consumption_statistics"
)
async def _async_setup(self) -> None:
self._suez_client = SuezClient(
@ -72,19 +94,165 @@ class SuezWaterCoordinator(DataUpdateCoordinator[SuezWaterData]):
try:
aggregated = await self._suez_client.fetch_aggregated_data()
data = SuezWaterData(
aggregated_value=aggregated.value,
aggregated_attr=SuezWaterAggregatedAttributes(
this_month_consumption=map_dict(aggregated.current_month),
previous_month_consumption=map_dict(aggregated.previous_month),
highest_monthly_consumption=aggregated.highest_monthly_consumption,
last_year_overall=aggregated.previous_year,
this_year_overall=aggregated.current_year,
history=map_dict(aggregated.history),
),
price=(await self._suez_client.get_price()).price,
)
except PySuezError as err:
raise UpdateFailed(f"Suez data update failed: {err}") from err
raise UpdateFailed("Suez coordinator error communicating with API") from err
price = None
try:
price = (await self._suez_client.get_price()).price
except PySuezError:
_LOGGER.debug("Failed to fetch water price", stack_info=True)
try:
await self._update_statistics(price)
except PySuezError as err:
raise UpdateFailed("Failed to update suez water statistics") from err
_LOGGER.debug("Successfully fetched suez data")
return data
return SuezWaterData(
aggregated_value=aggregated.value,
aggregated_attr=SuezWaterAggregatedAttributes(
this_month_consumption=map_dict(aggregated.current_month),
previous_month_consumption=map_dict(aggregated.previous_month),
highest_monthly_consumption=aggregated.highest_monthly_consumption,
last_year_overall=aggregated.previous_year,
this_year_overall=aggregated.current_year,
history=map_dict(aggregated.history),
),
price=price,
)
async def _update_statistics(self, current_price: float | None) -> None:
"""Update daily statistics."""
_LOGGER.debug("Updating statistics for %s", self._water_statistic_id)
water_last_stat = await self._get_last_stat(self._water_statistic_id)
cost_last_stat = await self._get_last_stat(self._cost_statistic_id)
consumption_sum = (
water_last_stat["sum"]
if water_last_stat and water_last_stat["sum"]
else 0.0
)
cost_sum = (
cost_last_stat["sum"] if cost_last_stat and cost_last_stat["sum"] else 0.0
)
last_stats = (
datetime.fromtimestamp(water_last_stat["start"]).date()
if water_last_stat
else None
)
_LOGGER.debug(
"Updating suez stat since %s for %s",
str(last_stats),
water_last_stat,
)
if not (
usage := await self._suez_client.fetch_all_daily_data(
since=last_stats,
)
):
_LOGGER.debug("No recent usage data. Skipping update")
return
_LOGGER.debug("fetched data: %s", len(usage))
consumption_statistics, cost_statistics = self._build_statistics(
current_price, consumption_sum, cost_sum, last_stats, usage
)
self._persist_statistics(consumption_statistics, cost_statistics)
def _build_statistics(
self,
current_price: float | None,
consumption_sum: float,
cost_sum: float,
last_stats: date | None,
usage: list[TelemetryMeasure],
) -> tuple[list[StatisticData], list[StatisticData]]:
"""Build statistics data from fetched data."""
consumption_statistics = []
cost_statistics = []
for data in usage:
if (
(last_stats is not None and data.date <= last_stats)
or not data.index
or data.volume is None
):
continue
consumption_date = dt_util.start_of_local_day(data.date)
consumption_sum += data.volume
consumption_statistics.append(
StatisticData(
start=consumption_date,
state=data.volume,
sum=consumption_sum,
)
)
if current_price is not None:
day_cost = (data.volume / 1000) * current_price
cost_sum += day_cost
cost_statistics.append(
StatisticData(
start=consumption_date,
state=day_cost,
sum=cost_sum,
)
)
return consumption_statistics, cost_statistics
def _persist_statistics(
self,
consumption_statistics: list[StatisticData],
cost_statistics: list[StatisticData],
) -> None:
"""Persist given statistics in recorder."""
consumption_metadata = self._get_statistics_metadata(
id=self._water_statistic_id, name="Consumption", unit=UnitOfVolume.LITERS
)
_LOGGER.debug(
"Adding %s statistics for %s",
len(consumption_statistics),
self._water_statistic_id,
)
async_add_external_statistics(
self.hass, consumption_metadata, consumption_statistics
)
if len(cost_statistics) > 0:
_LOGGER.debug(
"Adding %s statistics for %s",
len(cost_statistics),
self._cost_statistic_id,
)
cost_metadata = self._get_statistics_metadata(
id=self._cost_statistic_id, name="Cost", unit=CURRENCY_EURO
)
async_add_external_statistics(self.hass, cost_metadata, cost_statistics)
_LOGGER.debug("Updated statistics for %s", self._water_statistic_id)
def _get_statistics_metadata(
self, id: str, name: str, unit: str
) -> StatisticMetaData:
"""Build statistics metadata for requested configuration."""
return StatisticMetaData(
has_mean=False,
mean_type=StatisticMeanType.NONE,
has_sum=True,
name=f"Suez water {name} {self._counter_id}",
source=DOMAIN,
statistic_id=id,
unit_of_measurement=unit,
)
async def _get_last_stat(self, id: str) -> StatisticsRow | None:
"""Find last registered statistics of given id."""
last_stat = await get_instance(self.hass).async_add_executor_job(
get_last_statistics, self.hass, 1, id, True, {"sum"}
)
return last_stat[id][0] if last_stat else None

View File

@ -1,6 +1,7 @@
{
"domain": "suez_water",
"name": "Suez Water",
"after_dependencies": ["recorder"],
"codeowners": ["@ooii", "@jb101010-2"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/suez_water",

View File

@ -87,6 +87,14 @@ class SuezWaterSensor(CoordinatorEntity[SuezWaterCoordinator], SensorEntity):
)
self.entity_description = entity_description
@property
def available(self) -> bool:
"""Return if entity is available."""
return (
self.coordinator.last_update_success
and self.entity_description.value_fn(self.coordinator.data) is not None
)
@property
def native_value(self) -> float | str | None:
"""Return the state of the sensor."""

View File

@ -8,17 +8,26 @@ from pysuez import AggregatedData, PriceResult
from pysuez.const import ATTRIBUTION
import pytest
from homeassistant.components.recorder import Recorder
from homeassistant.components.suez_water.const import CONF_COUNTER_ID, DOMAIN
from tests.common import MockConfigEntry
from tests.conftest import RecorderInstanceContextManager
MOCK_DATA = {
"username": "test-username",
"password": "test-password",
CONF_COUNTER_ID: "test-counter",
CONF_COUNTER_ID: "123456",
}
@pytest.fixture
async def mock_recorder_before_hass(
async_test_recorder: RecorderInstanceContextManager,
) -> None:
"""Set up recorder."""
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Create mock config_entry needed by suez_water integration."""
@ -32,7 +41,7 @@ def mock_config_entry() -> MockConfigEntry:
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
def mock_setup_entry(recorder_mock: Recorder) -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.suez_water.async_setup_entry", return_value=True
@ -41,7 +50,7 @@ def mock_setup_entry() -> Generator[AsyncMock]:
@pytest.fixture(name="suez_client")
def mock_suez_client() -> Generator[AsyncMock]:
def mock_suez_client(recorder_mock: Recorder) -> Generator[AsyncMock]:
"""Create mock for suez_water external api."""
with (
patch(

View File

@ -0,0 +1,231 @@
# serializer version: 1
# name: test_statistics[water_consumption_statistics][test_statistics_call1]
defaultdict({
'suez_water:123456_water_consumption_statistics': list([
dict({
'end': 1733043600.0,
'last_reset': None,
'start': 1733040000.0,
'state': 500.0,
'sum': 500.0,
}),
dict({
'end': 1733130000.0,
'last_reset': None,
'start': 1733126400.0,
'state': 500.0,
'sum': 1000.0,
}),
dict({
'end': 1733216400.0,
'last_reset': None,
'start': 1733212800.0,
'state': 500.0,
'sum': 1500.0,
}),
]),
})
# ---
# name: test_statistics[water_consumption_statistics][test_statistics_call2]
defaultdict({
'suez_water:123456_water_consumption_statistics': list([
dict({
'end': 1733043600.0,
'last_reset': None,
'start': 1733040000.0,
'state': 500.0,
'sum': 500.0,
}),
dict({
'end': 1733130000.0,
'last_reset': None,
'start': 1733126400.0,
'state': 500.0,
'sum': 1000.0,
}),
dict({
'end': 1733216400.0,
'last_reset': None,
'start': 1733212800.0,
'state': 500.0,
'sum': 1500.0,
}),
]),
})
# ---
# name: test_statistics[water_consumption_statistics][test_statistics_call3]
defaultdict({
'suez_water:123456_water_consumption_statistics': list([
dict({
'end': 1733043600.0,
'last_reset': None,
'start': 1733040000.0,
'state': 500.0,
'sum': 500.0,
}),
dict({
'end': 1733130000.0,
'last_reset': None,
'start': 1733126400.0,
'state': 500.0,
'sum': 1000.0,
}),
dict({
'end': 1733216400.0,
'last_reset': None,
'start': 1733212800.0,
'state': 500.0,
'sum': 1500.0,
}),
]),
})
# ---
# name: test_statistics[water_consumption_statistics][test_statistics_call4]
defaultdict({
'suez_water:123456_water_consumption_statistics': list([
dict({
'end': 1733043600.0,
'last_reset': None,
'start': 1733040000.0,
'state': 500.0,
'sum': 500.0,
}),
dict({
'end': 1733130000.0,
'last_reset': None,
'start': 1733126400.0,
'state': 500.0,
'sum': 1000.0,
}),
dict({
'end': 1733216400.0,
'last_reset': None,
'start': 1733212800.0,
'state': 500.0,
'sum': 1500.0,
}),
dict({
'end': 1733389200.0,
'last_reset': None,
'start': 1733385600.0,
'state': 500.0,
'sum': 2000.0,
}),
]),
})
# ---
# name: test_statistics[water_cost_statistics][test_statistics_call1]
defaultdict({
'suez_water:123456_water_cost_statistics': list([
dict({
'end': 1733043600.0,
'last_reset': None,
'start': 1733040000.0,
'state': 2.37,
'sum': 2.37,
}),
dict({
'end': 1733130000.0,
'last_reset': None,
'start': 1733126400.0,
'state': 2.37,
'sum': 4.74,
}),
dict({
'end': 1733216400.0,
'last_reset': None,
'start': 1733212800.0,
'state': 2.37,
'sum': 7.11,
}),
]),
})
# ---
# name: test_statistics[water_cost_statistics][test_statistics_call2]
defaultdict({
'suez_water:123456_water_cost_statistics': list([
dict({
'end': 1733043600.0,
'last_reset': None,
'start': 1733040000.0,
'state': 2.37,
'sum': 2.37,
}),
dict({
'end': 1733130000.0,
'last_reset': None,
'start': 1733126400.0,
'state': 2.37,
'sum': 4.74,
}),
dict({
'end': 1733216400.0,
'last_reset': None,
'start': 1733212800.0,
'state': 2.37,
'sum': 7.11,
}),
]),
})
# ---
# name: test_statistics[water_cost_statistics][test_statistics_call3]
defaultdict({
'suez_water:123456_water_cost_statistics': list([
dict({
'end': 1733043600.0,
'last_reset': None,
'start': 1733040000.0,
'state': 2.37,
'sum': 2.37,
}),
dict({
'end': 1733130000.0,
'last_reset': None,
'start': 1733126400.0,
'state': 2.37,
'sum': 4.74,
}),
dict({
'end': 1733216400.0,
'last_reset': None,
'start': 1733212800.0,
'state': 2.37,
'sum': 7.11,
}),
]),
})
# ---
# name: test_statistics[water_cost_statistics][test_statistics_call4]
defaultdict({
'suez_water:123456_water_cost_statistics': list([
dict({
'end': 1733043600.0,
'last_reset': None,
'start': 1733040000.0,
'state': 2.37,
'sum': 2.37,
}),
dict({
'end': 1733130000.0,
'last_reset': None,
'start': 1733126400.0,
'state': 2.37,
'sum': 4.74,
}),
dict({
'end': 1733216400.0,
'last_reset': None,
'start': 1733212800.0,
'state': 2.37,
'sum': 7.11,
}),
dict({
'end': 1733389200.0,
'last_reset': None,
'start': 1733385600.0,
'state': 2.37,
'sum': 9.48,
}),
]),
})
# ---

View File

@ -29,7 +29,7 @@
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'water_price',
'unique_id': 'test-counter_water_price',
'unique_id': '123456_water_price',
'unit_of_measurement': '€',
})
# ---
@ -79,7 +79,7 @@
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'water_usage_yesterday',
'unique_id': 'test-counter_water_usage_yesterday',
'unique_id': '123456_water_usage_yesterday',
'unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
})
# ---

View File

@ -6,6 +6,7 @@ from pysuez.exception import PySuezError
import pytest
from homeassistant import config_entries
from homeassistant.components.recorder import Recorder
from homeassistant.components.suez_water.const import CONF_COUNTER_ID, DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
@ -70,7 +71,7 @@ async def test_form_invalid_auth(
async def test_form_already_configured(
hass: HomeAssistant, suez_client: AsyncMock
hass: HomeAssistant, recorder_mock: Recorder, suez_client: AsyncMock
) -> None:
"""Test we abort when entry is already configured."""

View File

@ -1,30 +1,32 @@
"""Test Suez_water integration initialization."""
from datetime import datetime, timedelta
from unittest.mock import AsyncMock
from homeassistant.components.suez_water.const import CONF_COUNTER_ID, DOMAIN
from homeassistant.components.suez_water.coordinator import PySuezError
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.recorder.statistics import statistics_during_period
from homeassistant.components.suez_water.const import (
CONF_COUNTER_ID,
DATA_REFRESH_INTERVAL,
DOMAIN,
)
from homeassistant.components.suez_water.coordinator import (
PySuezError,
TelemetryMeasure,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_USERNAME
from homeassistant.core import HomeAssistant
import homeassistant.util.dt as dt_util
from . import setup_integration
from .conftest import MOCK_DATA
from tests.common import MockConfigEntry
async def test_initialization_invalid_credentials(
hass: HomeAssistant,
suez_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that suez_water can't be loaded with invalid credentials."""
suez_client.check_credentials.return_value = False
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.components.recorder.common import async_wait_recording_done
async def test_initialization_setup_api_error(
@ -40,6 +42,210 @@ async def test_initialization_setup_api_error(
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
async def test_init_auth_failed(
hass: HomeAssistant,
suez_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that suez_water reflect authentication failure."""
suez_client.check_credentials.return_value = False
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
async def test_init_refresh_failed(
hass: HomeAssistant,
suez_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that suez_water reflect authentication failure."""
suez_client.fetch_aggregated_data.side_effect = PySuezError("Update failed")
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
async def test_init_statistics_failed(
hass: HomeAssistant,
suez_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that suez_water reflect authentication failure."""
suez_client.fetch_all_daily_data.side_effect = PySuezError("Update failed")
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
@pytest.mark.usefixtures("recorder_mock")
async def test_statistics_no_price(
hass: HomeAssistant,
suez_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that suez_water statistics does not register when no price."""
# New data retrieved but no price
suez_client.get_price.side_effect = PySuezError("will fail")
suez_client.fetch_all_daily_data.return_value = [
TelemetryMeasure(
(datetime.now().date()).strftime("%Y-%m-%d %H:%M:%S"), 0.5, 0.5
)
]
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.LOADED
statistic_id = (
f"{DOMAIN}:{mock_config_entry.data[CONF_COUNTER_ID]}_water_cost_statistics"
)
stats = await hass.async_add_executor_job(
statistics_during_period,
hass,
datetime.now() - timedelta(days=1),
None,
[statistic_id],
"hour",
None,
{"start", "state", "mean", "min", "max", "last_reset", "sum"},
)
assert stats.get(statistic_id) is None
@pytest.mark.usefixtures("recorder_mock")
@pytest.mark.parametrize(
"statistic",
[
"water_cost_statistics",
"water_consumption_statistics",
],
)
async def test_statistics(
hass: HomeAssistant,
suez_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
statistic: str,
) -> None:
"""Test that suez_water statistics are working."""
nb_samples = 3
start = datetime.fromisoformat("2024-12-04T02:00:00.0")
freezer.move_to(start)
origin = dt_util.start_of_local_day(start.date()) - timedelta(days=nb_samples)
result = [
TelemetryMeasure(
date=((origin + timedelta(days=d)).date()).strftime("%Y-%m-%d %H:%M:%S"),
volume=0.5,
index=0.5 * (d + 1),
)
for d in range(nb_samples)
]
suez_client.fetch_all_daily_data.return_value = result
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.LOADED
# Init data retrieved
await _test_for_data(
hass,
suez_client,
snapshot,
statistic,
origin,
mock_config_entry.data[CONF_COUNTER_ID],
1,
)
# No new data retrieved
suez_client.fetch_all_daily_data.return_value = []
freezer.tick(DATA_REFRESH_INTERVAL)
async_fire_time_changed(hass)
await _test_for_data(
hass,
suez_client,
snapshot,
statistic,
origin,
mock_config_entry.data[CONF_COUNTER_ID],
2,
)
# Old data retrieved
suez_client.fetch_all_daily_data.return_value = [
TelemetryMeasure(
date=(origin.date() - timedelta(days=1)).strftime("%Y-%m-%d %H:%M:%S"),
volume=0.5,
index=0.5 * (121 + 1),
)
]
freezer.tick(DATA_REFRESH_INTERVAL)
async_fire_time_changed(hass)
await _test_for_data(
hass,
suez_client,
snapshot,
statistic,
origin,
mock_config_entry.data[CONF_COUNTER_ID],
3,
)
# New daily data retrieved
suez_client.fetch_all_daily_data.return_value = [
TelemetryMeasure(
date=(datetime.now().date()).strftime("%Y-%m-%d %H:%M:%S"),
volume=0.5,
index=0.5 * (121 + 1),
)
]
freezer.tick(DATA_REFRESH_INTERVAL)
async_fire_time_changed(hass)
await _test_for_data(
hass,
suez_client,
snapshot,
statistic,
origin,
mock_config_entry.data[CONF_COUNTER_ID],
4,
)
async def _test_for_data(
hass: HomeAssistant,
suez_client: AsyncMock,
snapshot: SnapshotAssertion,
statistic: str,
origin: datetime,
counter_id: str,
nb_calls: int,
) -> None:
await hass.async_block_till_done(True)
await async_wait_recording_done(hass)
assert suez_client.fetch_all_daily_data.call_count == nb_calls
statistic_id = f"{DOMAIN}:{counter_id}_{statistic}"
stats = await hass.async_add_executor_job(
statistics_during_period,
hass,
origin - timedelta(days=1),
None,
[statistic_id],
"hour",
None,
{"start", "state", "mean", "min", "max", "last_reset", "sum"},
)
assert stats == snapshot(name=f"test_statistics_call{nb_calls}")
async def test_migration_version_rollback(
hass: HomeAssistant,
suez_client: AsyncMock,

View File

@ -41,16 +41,23 @@ async def test_sensors_valid_state(
assert previous.get(str(date.fromisoformat("2024-12-01"))) == 154
@pytest.mark.parametrize("method", [("fetch_aggregated_data"), ("get_price")])
@pytest.mark.parametrize(
("method", "price_on_error", "consumption_on_error"),
[
("fetch_aggregated_data", STATE_UNAVAILABLE, STATE_UNAVAILABLE),
("get_price", STATE_UNAVAILABLE, "160"),
],
)
async def test_sensors_failed_update(
hass: HomeAssistant,
suez_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
method: str,
price_on_error: str,
consumption_on_error: str,
) -> None:
"""Test that suez_water sensor reflect failure when api fails."""
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.LOADED
@ -58,10 +65,10 @@ async def test_sensors_failed_update(
entity_ids = await hass.async_add_executor_job(hass.states.entity_ids)
assert len(entity_ids) == 2
for entity in entity_ids:
state = hass.states.get(entity)
assert entity
assert state.state != STATE_UNAVAILABLE
state = hass.states.get("sensor.suez_mock_device_water_price")
assert state.state == "4.74"
state = hass.states.get("sensor.suez_mock_device_water_usage_yesterday")
assert state.state == "160"
getattr(suez_client, method).side_effect = PySuezError("Should fail to update")
@ -69,7 +76,7 @@ async def test_sensors_failed_update(
async_fire_time_changed(hass)
await hass.async_block_till_done(True)
for entity in entity_ids:
state = hass.states.get(entity)
assert entity
assert state.state == STATE_UNAVAILABLE
state = hass.states.get("sensor.suez_mock_device_water_price")
assert state.state == price_on_error
state = hass.states.get("sensor.suez_mock_device_water_usage_yesterday")
assert state.state == consumption_on_error