diff --git a/.strict-typing b/.strict-typing index 7b1fc47b3a3..cf77894f77f 100644 --- a/.strict-typing +++ b/.strict-typing @@ -151,6 +151,7 @@ homeassistant.components.oncue.* homeassistant.components.onewire.* homeassistant.components.open_meteo.* homeassistant.components.openuv.* +homeassistant.components.peco.* homeassistant.components.overkiz.* homeassistant.components.persistent_notification.* homeassistant.components.pi_hole.* diff --git a/CODEOWNERS b/CODEOWNERS index 24a7793f6a6..6ce57254f5a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -741,6 +741,8 @@ homeassistant/components/panel_custom/* @home-assistant/frontend tests/components/panel_custom/* @home-assistant/frontend homeassistant/components/panel_iframe/* @home-assistant/frontend tests/components/panel_iframe/* @home-assistant/frontend +homeassistant/components/peco/* @IceBotYT +tests/components/peco/* @IceBotYT homeassistant/components/persistent_notification/* @home-assistant/core tests/components/persistent_notification/* @home-assistant/core homeassistant/components/philips_js/* @elupus diff --git a/homeassistant/components/peco/__init__.py b/homeassistant/components/peco/__init__.py new file mode 100644 index 00000000000..86fe8c82d90 --- /dev/null +++ b/homeassistant/components/peco/__init__.py @@ -0,0 +1,31 @@ +"""The PECO Outage Counter integration.""" +from __future__ import annotations + +from typing import Final + +import peco + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant + +from .const import DOMAIN + +PLATFORMS: Final = [Platform.SENSOR] + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up PECO Outage Counter from a config entry.""" + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = peco.PecoOutageApi() + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/peco/config_flow.py b/homeassistant/components/peco/config_flow.py new file mode 100644 index 00000000000..1d3ab9cb5d3 --- /dev/null +++ b/homeassistant/components/peco/config_flow.py @@ -0,0 +1,43 @@ +"""Config flow for PECO Outage Counter integration.""" +from __future__ import annotations + +from typing import Any + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.data_entry_flow import FlowResult + +from .const import CONF_COUNTY, COUNTY_LIST, DOMAIN + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_COUNTY): vol.In(COUNTY_LIST), + } +) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for PECO Outage Counter.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA + ) + + county = user_input[ + CONF_COUNTY + ] # Voluptuous automatically detects if the county is invalid + + await self.async_set_unique_id(county) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=f"{county.capitalize()} Outage Count", data=user_input + ) diff --git a/homeassistant/components/peco/const.py b/homeassistant/components/peco/const.py new file mode 100644 index 00000000000..5f49ecf2898 --- /dev/null +++ b/homeassistant/components/peco/const.py @@ -0,0 +1,17 @@ +"""Constants for the PECO Outage Counter integration.""" +import logging +from typing import Final + +DOMAIN: Final = "peco" +LOGGER: Final = logging.getLogger(__package__) +COUNTY_LIST: Final = [ + "BUCKS", + "CHESTER", + "DELAWARE", + "MONTGOMERY", + "PHILADELPHIA", + "YORK", + "TOTAL", +] +SCAN_INTERVAL: Final = 9 +CONF_COUNTY: Final = "county" diff --git a/homeassistant/components/peco/manifest.json b/homeassistant/components/peco/manifest.json new file mode 100644 index 00000000000..2a577faddbf --- /dev/null +++ b/homeassistant/components/peco/manifest.json @@ -0,0 +1,13 @@ +{ + "domain": "peco", + "name": "PECO Outage Counter", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/peco", + "codeowners": [ + "@IceBotYT" + ], + "iot_class": "cloud_polling", + "requirements": [ + "peco==0.0.21" + ] +} \ No newline at end of file diff --git a/homeassistant/components/peco/sensor.py b/homeassistant/components/peco/sensor.py new file mode 100644 index 00000000000..d4490cd5a9b --- /dev/null +++ b/homeassistant/components/peco/sensor.py @@ -0,0 +1,113 @@ +"""Sensor component for PECO outage counter.""" +import asyncio +from datetime import timedelta +from typing import Final, cast + +from peco import BadJSONError, HttpError + +from homeassistant.components.sensor import ( + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) + +from .const import CONF_COUNTY, DOMAIN, LOGGER, SCAN_INTERVAL + +PARALLEL_UPDATES: Final = 0 +SENSOR_LIST = ( + SensorEntityDescription(key="customers_out", name="Customers Out"), + SensorEntityDescription( + key="percent_customers_out", + name="Percent Customers Out", + native_unit_of_measurement=PERCENTAGE, + ), + SensorEntityDescription(key="outage_count", name="Outage Count"), + SensorEntityDescription(key="customers_served", name="Customers Served"), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the sensor platform.""" + api = hass.data[DOMAIN][config_entry.entry_id] + websession = async_get_clientsession(hass) + county: str = config_entry.data[CONF_COUNTY] + + async def async_update_data() -> dict[str, float]: + """Fetch data from API.""" + try: + data = ( + cast(dict[str, float], await api.get_outage_totals(websession)) + if county == "TOTAL" + else cast( + dict[str, float], + await api.get_outage_count(county, websession), + ) + ) + except HttpError as err: + raise UpdateFailed(f"Error fetching data: {err}") from err + except BadJSONError as err: + raise UpdateFailed(f"Error parsing data: {err}") from err + except asyncio.TimeoutError as err: + raise UpdateFailed(f"Timeout fetching data: {err}") from err + if data["percent_customers_out"] < 5: + percent_out = round( + data["customers_out"] / data["customers_served"] * 100, 3 + ) + data["percent_customers_out"] = percent_out + return data + + coordinator = DataUpdateCoordinator( + hass, + LOGGER, + name="PECO Outage Count", + update_method=async_update_data, + update_interval=timedelta(minutes=SCAN_INTERVAL), + ) + + await coordinator.async_config_entry_first_refresh() + + async_add_entities( + [PecoSensor(sensor, county, coordinator) for sensor in SENSOR_LIST], + True, + ) + return + + +class PecoSensor( + CoordinatorEntity[DataUpdateCoordinator[dict[str, float]]], SensorEntity +): + """PECO outage counter sensor.""" + + _attr_state_class = SensorStateClass.MEASUREMENT + _attr_icon: str = "mdi:power-plug-off" + + def __init__( + self, + description: SensorEntityDescription, + county: str, + coordinator: DataUpdateCoordinator[dict[str, float]], + ) -> None: + """Initialize the sensor.""" + super().__init__(coordinator) + self._attr_name = f"{county.capitalize()} {description.name}" + self._attr_unique_id = f"{county}-{description.key}" + self.entity_description = description + + @property + def native_value(self) -> float: + """Return the value of the sensor.""" + return self.coordinator.data[self.entity_description.key] diff --git a/homeassistant/components/peco/strings.json b/homeassistant/components/peco/strings.json new file mode 100644 index 00000000000..2d195ed5d22 --- /dev/null +++ b/homeassistant/components/peco/strings.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "user": { + "title": "PECO Outage Counter", + "description": "Please choose your county below.", + "data": { + "county": "County" + } + } + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/peco/translations/en.json b/homeassistant/components/peco/translations/en.json new file mode 100644 index 00000000000..60483a1d65c --- /dev/null +++ b/homeassistant/components/peco/translations/en.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Service is already configured" + }, + "step": { + "user": { + "data": { + "county": "County" + }, + "description": "Please choose your county below.", + "title": "PECO Outage Counter" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 6a668b0d666..058bb936c7b 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -249,6 +249,7 @@ FLOWS = { "owntracks", "p1_monitor", "panasonic_viera", + "peco", "philips_js", "pi_hole", "picnic", diff --git a/mypy.ini b/mypy.ini index b4d51d7f4e1..2f1792438a5 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1463,6 +1463,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.peco.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.overkiz.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 64332f81b7e..013576a33db 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1177,6 +1177,9 @@ panasonic_viera==0.3.6 # homeassistant.components.dunehd pdunehd==1.3.2 +# homeassistant.components.peco +peco==0.0.21 + # homeassistant.components.pencom pencompy==0.0.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 4f5cf484ee0..148384e0fdd 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -768,6 +768,9 @@ panasonic_viera==0.3.6 # homeassistant.components.dunehd pdunehd==1.3.2 +# homeassistant.components.peco +peco==0.0.21 + # homeassistant.components.aruba # homeassistant.components.cisco_ios # homeassistant.components.pandora diff --git a/tests/components/peco/__init__.py b/tests/components/peco/__init__.py new file mode 100644 index 00000000000..090a46cdf7b --- /dev/null +++ b/tests/components/peco/__init__.py @@ -0,0 +1 @@ +"""Tests for the PECO Outage Counter integration.""" diff --git a/tests/components/peco/test_config_flow.py b/tests/components/peco/test_config_flow.py new file mode 100644 index 00000000000..bd209b54b1d --- /dev/null +++ b/tests/components/peco/test_config_flow.py @@ -0,0 +1,57 @@ +"""Test the PECO Outage Counter config flow.""" +from unittest.mock import patch + +from pytest import raises +from voluptuous.error import MultipleInvalid + +from homeassistant import config_entries +from homeassistant.components.peco.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM + + +async def test_form(hass: HomeAssistant) -> None: + """Test we get the form.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + with patch( + "homeassistant.components.peco.async_setup_entry", + return_value=True, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "county": "PHILADELPHIA", + }, + ) + await hass.async_block_till_done() + + assert result2["type"] == RESULT_TYPE_CREATE_ENTRY + assert result2["title"] == "Philadelphia Outage Count" + assert result2["data"] == { + "county": "PHILADELPHIA", + } + + +async def test_invalid_county(hass: HomeAssistant) -> None: + """Test if the InvalidCounty error works.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == RESULT_TYPE_FORM + assert result["errors"] is None + + with raises(MultipleInvalid): + await hass.config_entries.flow.async_configure( + result["flow_id"], + { + "county": "INVALID_COUNTY_THAT_SHOULD_NOT_EXIST", + }, + ) + await hass.async_block_till_done() + + # it should have errored, instead of returning an errors dict, since this error should never happen diff --git a/tests/components/peco/test_init.py b/tests/components/peco/test_init.py new file mode 100644 index 00000000000..e7db50e653c --- /dev/null +++ b/tests/components/peco/test_init.py @@ -0,0 +1,38 @@ +"""Test the PECO Outage Counter init file.""" +from peco import PecoOutageApi + +from homeassistant.components.peco.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + +MOCK_ENTRY_DATA = {"county": "TOTAL"} +INVALID_COUNTY_DATA = {"county": "INVALID_COUNTY_THAT_SHOULD_NOT_EXIST", "test": True} + + +async def test_unload_entry(hass: HomeAssistant) -> None: + """Test the unload entry.""" + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + assert hass.data[DOMAIN] + + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + assert entries[0].state == ConfigEntryState.LOADED + + await hass.config_entries.async_unload(entries[0].entry_id) + await hass.async_block_till_done() + assert entries[0].state == ConfigEntryState.NOT_LOADED + + +async def test_data(hass: HomeAssistant) -> None: + """Test the data.""" + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + assert hass.data[DOMAIN] + assert isinstance(hass.data[DOMAIN][config_entry.entry_id], PecoOutageApi) diff --git a/tests/components/peco/test_sensor.py b/tests/components/peco/test_sensor.py new file mode 100644 index 00000000000..ea879c8f07a --- /dev/null +++ b/tests/components/peco/test_sensor.py @@ -0,0 +1,256 @@ +"""Test the PECO Outage Counter sensors.""" +import asyncio + +from homeassistant.components.peco.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry +from tests.test_util.aiohttp import AiohttpClientMocker + +MOCK_ENTRY_DATA = {"county": "TOTAL"} +COUNTY_ENTRY_DATA = {"county": "BUCKS"} +INVALID_COUNTY_DATA = {"county": "INVALID"} + + +async def test_sensor_available( + aioclient_mock: AiohttpClientMocker, hass: HomeAssistant +) -> None: + """Test that the sensors are working.""" + # Totals Test + aioclient_mock.get( + "https://kubra.io/stormcenter/api/v1/stormcenters/39e6d9f3-fdea-4539-848f-b8631945da6f/views/74de8a50-3f45-4f6a-9483-fd618bb9165d/currentState?preview=false", + json={"data": {"interval_generation_data": "data/TEST"}}, + ) + aioclient_mock.get( + "https://kubra.io/data/TEST/public/reports/a36a6292-1c55-44de-a6a9-44fedf9482ee_report.json", + json={ + "file_data": { + "totals": { + "cust_a": { + "val": 123, + }, + "percent_cust_a": { + "val": 1.23, + }, + "n_out": 456, + "cust_s": 789, + } + } + }, + ) + + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.data[DOMAIN] + + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + assert config_entry.state == ConfigEntryState.LOADED + + sensors_to_get = [ + "total_customers_out", + "total_percent_customers_out", + "total_outage_count", + "total_customers_served", + ] + + for sensor in sensors_to_get: + sensor_entity = hass.states.get(f"sensor.{sensor}") + assert sensor_entity is not None + assert sensor_entity.state != "unavailable" + + if sensor == "total_customers_out": + assert sensor_entity.state == "123" + elif sensor == "total_percent_customers_out": + assert sensor_entity.state == "15.589" + elif sensor == "total_outage_count": + assert sensor_entity.state == "456" + elif sensor == "total_customers_served": + assert sensor_entity.state == "789" + + # County Test + aioclient_mock.clear_requests() + aioclient_mock.get( + "https://kubra.io/stormcenter/api/v1/stormcenters/39e6d9f3-fdea-4539-848f-b8631945da6f/views/74de8a50-3f45-4f6a-9483-fd618bb9165d/currentState?preview=false", + json={"data": {"interval_generation_data": "data/TEST"}}, + ) + aioclient_mock.get( + "https://kubra.io/data/TEST/public/reports/a36a6292-1c55-44de-a6a9-44fedf9482ee_report.json", + json={ + "file_data": { + "areas": [ + { + "name": "BUCKS", + "cust_a": { + "val": 123, + }, + "percent_cust_a": { + "val": 1.23, + }, + "n_out": 456, + "cust_s": 789, + } + ] + } + }, + ) + + config_entry = MockConfigEntry(domain=DOMAIN, data=COUNTY_ENTRY_DATA) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.data[DOMAIN] + + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 2 + assert config_entry.state == ConfigEntryState.LOADED + + sensors_to_get = [ + "bucks_customers_out", + "bucks_percent_customers_out", + "bucks_outage_count", + "bucks_customers_served", + ] + + for sensor in sensors_to_get: + sensor_entity = hass.states.get(f"sensor.{sensor}") + assert sensor_entity is not None + assert sensor_entity.state != "unavailable" + + if sensor == "bucks_customers_out": + assert sensor_entity.state == "123" + elif sensor == "bucks_percent_customers_out": + assert sensor_entity.state == "15.589" + elif sensor == "bucks_outage_count": + assert sensor_entity.state == "456" + elif sensor == "bucks_customers_served": + assert sensor_entity.state == "789" + + +async def test_http_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog +): + """Test if it raises an error when there is an HTTP error.""" + aioclient_mock.get( + "https://kubra.io/stormcenter/api/v1/stormcenters/39e6d9f3-fdea-4539-848f-b8631945da6f/views/74de8a50-3f45-4f6a-9483-fd618bb9165d/currentState?preview=false", + json={"data": {"interval_generation_data": "data/TEST"}}, + ) + aioclient_mock.get( + "https://kubra.io/data/TEST/public/reports/a36a6292-1c55-44de-a6a9-44fedf9482ee_report.json", + status=500, + json={"error": "Internal Server Error"}, + ) + + config_entry = MockConfigEntry(domain=DOMAIN, data=COUNTY_ENTRY_DATA) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.data[DOMAIN] + + assert "Error getting PECO outage counter data" in caplog.text + + +async def test_bad_json( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog +): + """Test if it raises an error when there is bad JSON.""" + aioclient_mock.get( + "https://kubra.io/stormcenter/api/v1/stormcenters/39e6d9f3-fdea-4539-848f-b8631945da6f/views/74de8a50-3f45-4f6a-9483-fd618bb9165d/currentState?preview=false", + json={"data": {}}, + ) + + config_entry = MockConfigEntry(domain=DOMAIN, data=COUNTY_ENTRY_DATA) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.data[DOMAIN] + + assert "ConfigEntryNotReady" in caplog.text + + +async def test_total_http_error( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog +): + """Test if it raises an error when there is an HTTP error.""" + aioclient_mock.get( + "https://kubra.io/stormcenter/api/v1/stormcenters/39e6d9f3-fdea-4539-848f-b8631945da6f/views/74de8a50-3f45-4f6a-9483-fd618bb9165d/currentState?preview=false", + json={"data": {"interval_generation_data": "data/TEST"}}, + ) + aioclient_mock.get( + "https://kubra.io/data/TEST/public/reports/a36a6292-1c55-44de-a6a9-44fedf9482ee_report.json", + status=500, + json={"error": "Internal Server Error"}, + ) + + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.data[DOMAIN] + + assert "Error getting PECO outage counter data" in caplog.text + + +async def test_total_bad_json( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog +): + """Test if it raises an error when there is bad JSON.""" + aioclient_mock.get( + "https://kubra.io/stormcenter/api/v1/stormcenters/39e6d9f3-fdea-4539-848f-b8631945da6f/views/74de8a50-3f45-4f6a-9483-fd618bb9165d/currentState?preview=false", + json={"data": {}}, + ) + + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.data[DOMAIN] + + assert "ConfigEntryNotReady" in caplog.text + + +async def test_update_timeout( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog +): + """Test if it raises an error when there is a timeout.""" + aioclient_mock.get( + "https://kubra.io/stormcenter/api/v1/stormcenters/39e6d9f3-fdea-4539-848f-b8631945da6f/views/74de8a50-3f45-4f6a-9483-fd618bb9165d/currentState?preview=false", + exc=asyncio.TimeoutError(), + ) + + config_entry = MockConfigEntry(domain=DOMAIN, data=COUNTY_ENTRY_DATA) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.data[DOMAIN] + + assert "Timeout fetching data" in caplog.text + + +async def test_total_update_timeout( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog +): + """Test if it raises an error when there is a timeout.""" + aioclient_mock.get( + "https://kubra.io/stormcenter/api/v1/stormcenters/39e6d9f3-fdea-4539-848f-b8631945da6f/views/74de8a50-3f45-4f6a-9483-fd618bb9165d/currentState?preview=false", + exc=asyncio.TimeoutError(), + ) + + config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_ENTRY_DATA) + config_entry.add_to_hass(hass) + + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert hass.data[DOMAIN] + + assert "Timeout fetching data" in caplog.text