mirror of
https://github.com/home-assistant/core.git
synced 2026-04-13 21:26:19 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30a554a242 | ||
|
|
888ec5e965 | ||
|
|
6396744f19 | ||
|
|
308aa7b868 | ||
|
|
a74c3d41b9 | ||
|
|
e6d9f80c9e | ||
|
|
8789afe21f | ||
|
|
1fbf437c49 | ||
|
|
f8e5165ec7 |
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioamazondevices==13.4.0"]
|
||||
"requirements": ["aioamazondevices==13.4.1"]
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from . import SIGNAL_CONNECTED, AppleTvConfigEntry
|
||||
from .entity import AppleTVEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -16,6 +16,7 @@ from .const import DOMAIN
|
||||
from .coordinator import CometBlueConfigEntry, CometBlueDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.BUTTON,
|
||||
Platform.CLIMATE,
|
||||
]
|
||||
|
||||
|
||||
61
homeassistant/components/eurotronic_cometblue/button.py
Normal file
61
homeassistant/components/eurotronic_cometblue/button.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Comet Blue button platform."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .coordinator import CometBlueConfigEntry, CometBlueDataUpdateCoordinator
|
||||
from .entity import CometBlueBluetoothEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
DESCRIPTIONS = [
|
||||
ButtonEntityDescription(
|
||||
key="sync_time",
|
||||
translation_key="sync_time",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: CometBlueConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the client entities."""
|
||||
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
CometBlueButtonEntity(coordinator, description)
|
||||
for description in DESCRIPTIONS
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class CometBlueButtonEntity(CometBlueBluetoothEntity, ButtonEntity):
|
||||
"""Representation of a button."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: CometBlueDataUpdateCoordinator,
|
||||
description: ButtonEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize CometBlueButtonEntity."""
|
||||
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.address}-{description.key}"
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Handle the button press."""
|
||||
if self.entity_description.key == "sync_time":
|
||||
await self.coordinator.send_command(
|
||||
self.coordinator.device.set_datetime_async, {"date": dt_util.now()}
|
||||
)
|
||||
9
homeassistant/components/eurotronic_cometblue/icons.json
Normal file
9
homeassistant/components/eurotronic_cometblue/icons.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entity": {
|
||||
"button": {
|
||||
"sync_time": {
|
||||
"default": "mdi:calendar-clock"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,5 +29,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"button": {
|
||||
"sync_time": {
|
||||
"name": "Sync time"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,6 @@ async def async_setup_entry(
|
||||
class JellyfinMediaPlayer(JellyfinClientEntity, MediaPlayerEntity):
|
||||
"""Represents a Jellyfin Player device."""
|
||||
|
||||
_attr_media_image_remotely_accessible = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: JellyfinDataUpdateCoordinator,
|
||||
|
||||
@@ -282,12 +282,16 @@ def _humanify(
|
||||
else:
|
||||
continue
|
||||
|
||||
time_fired_ts = row[TIME_FIRED_TS_POS]
|
||||
row_time_fired_ts = row[TIME_FIRED_TS_POS]
|
||||
# Explicit None check: 0.0 is a valid epoch.
|
||||
time_fired_ts: float = (
|
||||
row_time_fired_ts if row_time_fired_ts is not None else time.time()
|
||||
)
|
||||
if timestamp:
|
||||
when = time_fired_ts or time.time()
|
||||
when: str | float = time_fired_ts
|
||||
else:
|
||||
when = process_timestamp_to_utc_isoformat(
|
||||
dt_util.utc_from_timestamp(time_fired_ts) or dt_util.utcnow()
|
||||
dt_util.utc_from_timestamp(time_fired_ts)
|
||||
)
|
||||
data[LOGBOOK_ENTRY_WHEN] = when
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
|
||||
from luftdaten import Luftdaten
|
||||
from luftdaten.exceptions import LuftdatenError
|
||||
from luftdaten.exceptions import LuftdatenConnectionError, LuftdatenError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -47,11 +47,22 @@ class LuftdatenDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float | int
|
||||
"""Update sensor/binary sensor data."""
|
||||
try:
|
||||
await self._sensor_community.get_data()
|
||||
except LuftdatenConnectionError as err:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="communication_error",
|
||||
) from err
|
||||
except LuftdatenError as err:
|
||||
raise UpdateFailed("Unable to retrieve data from Sensor.Community") from err
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unknown_error",
|
||||
) from err
|
||||
|
||||
if not self._sensor_community.values:
|
||||
raise UpdateFailed("Did not receive sensor data from Sensor.Community")
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="no_data_received",
|
||||
)
|
||||
|
||||
data: dict[str, float | int] = self._sensor_community.values
|
||||
data.update(self._sensor_community.meta)
|
||||
|
||||
@@ -21,5 +21,16 @@
|
||||
"sensor": {
|
||||
"pressure_at_sealevel": { "name": "Pressure at sea level" }
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"communication_error": {
|
||||
"message": "An error occurred while communicating with the Sensor.Community service."
|
||||
},
|
||||
"no_data_received": {
|
||||
"message": "Did not receive sensor data from the Sensor.Community service."
|
||||
},
|
||||
"unknown_error": {
|
||||
"message": "An unknown error occurred while communicating with the Sensor.Community service."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ DEFAULT_NAME: Final = "Mastodon"
|
||||
ATTR_ACCOUNT_NAME = "account_name"
|
||||
ATTR_STATUS = "status"
|
||||
ATTR_VISIBILITY = "visibility"
|
||||
ATTR_QUOTE_APPROVAL_POLICY = "quote_approval_policy"
|
||||
ATTR_IDEMPOTENCY_KEY = "idempotency_key"
|
||||
ATTR_CONTENT_WARNING = "content_warning"
|
||||
ATTR_MEDIA_WARNING = "media_warning"
|
||||
|
||||
@@ -52,6 +52,7 @@ from .const import (
|
||||
ATTR_MEDIA_DESCRIPTION,
|
||||
ATTR_MEDIA_WARNING,
|
||||
ATTR_NOTE,
|
||||
ATTR_QUOTE_APPROVAL_POLICY,
|
||||
ATTR_STATUS,
|
||||
ATTR_VALUE,
|
||||
ATTR_VISIBILITY,
|
||||
@@ -73,6 +74,14 @@ class StatusVisibility(StrEnum):
|
||||
DIRECT = "direct"
|
||||
|
||||
|
||||
class QuoteApprovalPolicy(StrEnum):
|
||||
"""QuoteApprovalPolicy model."""
|
||||
|
||||
PUBLIC = "public"
|
||||
FOLLOWERS = "followers"
|
||||
NOBODY = "nobody"
|
||||
|
||||
|
||||
SERVICE_GET_ACCOUNT = "get_account"
|
||||
SERVICE_GET_ACCOUNT_SCHEMA = vol.Schema(
|
||||
{
|
||||
@@ -107,6 +116,9 @@ SERVICE_POST_SCHEMA = vol.Schema(
|
||||
vol.Required(ATTR_CONFIG_ENTRY_ID): str,
|
||||
vol.Required(ATTR_STATUS): str,
|
||||
vol.Optional(ATTR_VISIBILITY): vol.In([x.lower() for x in StatusVisibility]),
|
||||
vol.Optional(ATTR_QUOTE_APPROVAL_POLICY): vol.In(
|
||||
[x.lower() for x in QuoteApprovalPolicy]
|
||||
),
|
||||
vol.Optional(ATTR_IDEMPOTENCY_KEY): str,
|
||||
vol.Optional(ATTR_CONTENT_WARNING): str,
|
||||
vol.Optional(ATTR_LANGUAGE): str,
|
||||
@@ -287,6 +299,11 @@ async def _async_post(call: ServiceCall) -> ServiceResponse:
|
||||
if ATTR_VISIBILITY in call.data
|
||||
else None
|
||||
)
|
||||
quote_approval_policy: str | None = (
|
||||
QuoteApprovalPolicy(call.data[ATTR_QUOTE_APPROVAL_POLICY])
|
||||
if ATTR_QUOTE_APPROVAL_POLICY in call.data
|
||||
else None
|
||||
)
|
||||
idempotency_key: str | None = call.data.get(ATTR_IDEMPOTENCY_KEY)
|
||||
spoiler_text: str | None = call.data.get(ATTR_CONTENT_WARNING)
|
||||
language: str | None = call.data.get(ATTR_LANGUAGE)
|
||||
@@ -307,6 +324,7 @@ async def _async_post(call: ServiceCall) -> ServiceResponse:
|
||||
client=client,
|
||||
status=status,
|
||||
visibility=visibility,
|
||||
quote_approval_policy=quote_approval_policy,
|
||||
idempotency_key=idempotency_key,
|
||||
spoiler_text=spoiler_text,
|
||||
language=language,
|
||||
|
||||
@@ -50,6 +50,14 @@ post:
|
||||
- private
|
||||
- direct
|
||||
translation_key: post_visibility
|
||||
quote_approval_policy:
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- public
|
||||
- followers
|
||||
- nobody
|
||||
translation_key: quote_approval_policy
|
||||
idempotency_key:
|
||||
selector:
|
||||
text:
|
||||
|
||||
@@ -152,6 +152,13 @@
|
||||
"public": "Public - Visible to everyone",
|
||||
"unlisted": "Unlisted - Public but not shown in public timelines"
|
||||
}
|
||||
},
|
||||
"quote_approval_policy": {
|
||||
"options": {
|
||||
"followers": "Followers - Only accounts that follow you can quote this post",
|
||||
"nobody": "Nobody - No one but you can quote this post",
|
||||
"public": "Public - Anyone can quote this post"
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
@@ -222,6 +229,10 @@
|
||||
"description": "If an image or video is attached, will mark the media as sensitive (default: no media warning).",
|
||||
"name": "Media warning"
|
||||
},
|
||||
"quote_approval_policy": {
|
||||
"description": "Who can quote this post (default: account setting).\nIgnored if visibility is private or direct.",
|
||||
"name": "Who can quote"
|
||||
},
|
||||
"status": {
|
||||
"description": "The status to post.",
|
||||
"name": "Status"
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from vehicle import RDW, Vehicle
|
||||
from vehicle import RDW, RDWConnectionError, RDWError, Vehicle
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import CONF_LICENSE_PLATE, DOMAIN, LOGGER, SCAN_INTERVAL
|
||||
|
||||
@@ -35,4 +35,15 @@ class RDWDataUpdateCoordinator(DataUpdateCoordinator[Vehicle]):
|
||||
|
||||
async def _async_update_data(self) -> Vehicle:
|
||||
"""Fetch data from RDW."""
|
||||
return await self._rdw.vehicle()
|
||||
try:
|
||||
return await self._rdw.vehicle()
|
||||
except RDWConnectionError as err:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="communication_error",
|
||||
) from err
|
||||
except RDWError as err:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unknown_error",
|
||||
) from err
|
||||
|
||||
@@ -35,5 +35,13 @@
|
||||
"name": "Ascription date"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"communication_error": {
|
||||
"message": "An error occurred while communicating with the RDW service."
|
||||
},
|
||||
"unknown_error": {
|
||||
"message": "An unknown error occurred while communicating with the RDW service."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,6 +916,7 @@ async def update_listener(hass: HomeAssistant, entry: TelegramBotConfigEntry) ->
|
||||
if entry.runtime_data.old_config_data != entry.data:
|
||||
# Reload if config data has changed
|
||||
hass.config_entries.async_schedule_reload(entry.entry_id)
|
||||
return
|
||||
|
||||
# reload entities
|
||||
await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
@@ -24,9 +24,8 @@ from homeassistant.components.recorder.statistics import (
|
||||
statistics_during_period,
|
||||
)
|
||||
from homeassistant.const import UnitOfEnergy
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.unit_conversion import EnergyConverter
|
||||
@@ -256,7 +255,7 @@ class TibberDataCoordinator(DataUpdateCoordinator[None]):
|
||||
|
||||
|
||||
class TibberPriceCoordinator(DataUpdateCoordinator[dict[str, TibberHomeData]]):
|
||||
"""Handle Tibber price sensor updates."""
|
||||
"""Handle Tibber price data and insert statistics."""
|
||||
|
||||
config_entry: TibberConfigEntry
|
||||
|
||||
@@ -273,41 +272,7 @@ class TibberPriceCoordinator(DataUpdateCoordinator[dict[str, TibberHomeData]]):
|
||||
name=f"{DOMAIN} price",
|
||||
update_interval=timedelta(minutes=1),
|
||||
)
|
||||
self._tomorrow_price_poll_threshold_seconds = random.uniform(
|
||||
3600 * 14, 3600 * 23
|
||||
)
|
||||
self._unsub_tomorrow_price_poll: CALLBACK_TYPE | None = None
|
||||
initial_tomorrow_price_poll = dt_util.start_of_local_day() + timedelta(
|
||||
seconds=self._tomorrow_price_poll_threshold_seconds
|
||||
)
|
||||
if initial_tomorrow_price_poll <= dt_util.utcnow():
|
||||
initial_tomorrow_price_poll += timedelta(days=1)
|
||||
self._schedule_tomorrow_price_poll(initial_tomorrow_price_poll)
|
||||
self._tibber_homes: list[tibber.TibberHome] | None = None
|
||||
|
||||
async def async_shutdown(self) -> None:
|
||||
"""Cancel any scheduled call, and ignore new runs."""
|
||||
await super().async_shutdown()
|
||||
if self._unsub_tomorrow_price_poll:
|
||||
self._unsub_tomorrow_price_poll()
|
||||
self._unsub_tomorrow_price_poll = None
|
||||
|
||||
def _schedule_tomorrow_price_poll(self, point_in_time: datetime) -> None:
|
||||
"""Schedule the next one-shot tomorrow price poll."""
|
||||
if point_in_time <= (now := dt_util.utcnow()):
|
||||
point_in_time = now + timedelta(seconds=1)
|
||||
if self._unsub_tomorrow_price_poll:
|
||||
self._unsub_tomorrow_price_poll()
|
||||
self._unsub_tomorrow_price_poll = async_track_point_in_utc_time(
|
||||
self.hass,
|
||||
self._async_handle_tomorrow_price_poll,
|
||||
point_in_time,
|
||||
)
|
||||
|
||||
async def _async_handle_tomorrow_price_poll(self, _: datetime) -> None:
|
||||
"""Handle the scheduled tomorrow price poll."""
|
||||
self._unsub_tomorrow_price_poll = None
|
||||
await self._fetch_data()
|
||||
self._tomorrow_price_poll_threshold_seconds = random.uniform(0, 3600 * 10)
|
||||
|
||||
def _time_until_next_15_minute(self) -> timedelta:
|
||||
"""Return time until the next 15-minute boundary (0, 15, 30, 45) in UTC."""
|
||||
@@ -324,23 +289,7 @@ class TibberPriceCoordinator(DataUpdateCoordinator[dict[str, TibberHomeData]]):
|
||||
return next_run - now
|
||||
|
||||
async def _async_update_data(self) -> dict[str, TibberHomeData]:
|
||||
if self._tibber_homes is None:
|
||||
await self._fetch_data()
|
||||
|
||||
homes = self._tibber_homes
|
||||
if homes is None:
|
||||
raise UpdateFailed("No Tibber homes available")
|
||||
|
||||
result = {home.home_id: _build_home_data(home) for home in homes}
|
||||
|
||||
self.update_interval = self._time_until_next_15_minute()
|
||||
return result
|
||||
|
||||
async def _fetch_data(self) -> None:
|
||||
"""Fetch latest price data via API and update cached home data."""
|
||||
self._schedule_tomorrow_price_poll(
|
||||
dt_util.utcnow() + timedelta(seconds=random.uniform(60, 60 * 10))
|
||||
)
|
||||
"""Update data via API and return per-home data for sensors."""
|
||||
tibber_connection = await self.config_entry.runtime_data.async_get_client(
|
||||
self.hass
|
||||
)
|
||||
@@ -381,31 +330,21 @@ class TibberPriceCoordinator(DataUpdateCoordinator[dict[str, TibberHomeData]]):
|
||||
return False
|
||||
|
||||
homes_to_update = [home for home in active_homes if _needs_update(home)]
|
||||
|
||||
try:
|
||||
if homes_to_update:
|
||||
await asyncio.gather(
|
||||
*(home.update_info_and_price_info() for home in homes_to_update)
|
||||
)
|
||||
except tibber.exceptions.RateLimitExceededError as err:
|
||||
self._schedule_tomorrow_price_poll(
|
||||
dt_util.utcnow() + timedelta(seconds=err.retry_after)
|
||||
)
|
||||
raise UpdateFailed(
|
||||
f"Rate limit exceeded, retry after {err.retry_after} seconds",
|
||||
retry_after=err.retry_after,
|
||||
) from err
|
||||
except tibber.exceptions.HttpExceptionError as err:
|
||||
raise UpdateFailed(f"Error communicating with API ({err})") from err
|
||||
except tibber.RetryableHttpExceptionError as err:
|
||||
raise UpdateFailed(f"Error communicating with API ({err.status})") from err
|
||||
except tibber.FatalHttpExceptionError as err:
|
||||
raise UpdateFailed(f"Error communicating with API ({err.status})") from err
|
||||
|
||||
self._schedule_tomorrow_price_poll(
|
||||
dt_util.start_of_local_day(now)
|
||||
+ timedelta(days=1, seconds=self._tomorrow_price_poll_threshold_seconds)
|
||||
)
|
||||
self._tibber_homes = active_homes
|
||||
result = {home.home_id: _build_home_data(home) for home in active_homes}
|
||||
|
||||
|
||||
class TibberFetchPriceCoordinator(TibberPriceCoordinator):
|
||||
"""Backward-compatible alias for the merged price fetch coordinator."""
|
||||
self.update_interval = self._time_until_next_15_minute()
|
||||
return result
|
||||
|
||||
|
||||
class TibberDataAPICoordinator(DataUpdateCoordinator[dict[str, TibberDevice]]):
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["tibber"],
|
||||
"requirements": ["pyTibber==0.37.1"]
|
||||
"requirements": ["pyTibber==0.37.0"]
|
||||
}
|
||||
|
||||
4
requirements_all.txt
generated
4
requirements_all.txt
generated
@@ -190,7 +190,7 @@ aioairzone-cloud==0.7.2
|
||||
aioairzone==1.0.5
|
||||
|
||||
# homeassistant.components.alexa_devices
|
||||
aioamazondevices==13.4.0
|
||||
aioamazondevices==13.4.1
|
||||
|
||||
# homeassistant.components.ambient_network
|
||||
# homeassistant.components.ambient_station
|
||||
@@ -1928,7 +1928,7 @@ pyRFXtrx==0.31.1
|
||||
pySDCP==1
|
||||
|
||||
# homeassistant.components.tibber
|
||||
pyTibber==0.37.1
|
||||
pyTibber==0.37.0
|
||||
|
||||
# homeassistant.components.dlink
|
||||
pyW215==0.8.0
|
||||
|
||||
4
requirements_test_all.txt
generated
4
requirements_test_all.txt
generated
@@ -181,7 +181,7 @@ aioairzone-cloud==0.7.2
|
||||
aioairzone==1.0.5
|
||||
|
||||
# homeassistant.components.alexa_devices
|
||||
aioamazondevices==13.4.0
|
||||
aioamazondevices==13.4.1
|
||||
|
||||
# homeassistant.components.ambient_network
|
||||
# homeassistant.components.ambient_station
|
||||
@@ -1671,7 +1671,7 @@ pyHomee==1.3.8
|
||||
pyRFXtrx==0.31.1
|
||||
|
||||
# homeassistant.components.tibber
|
||||
pyTibber==0.37.1
|
||||
pyTibber==0.37.0
|
||||
|
||||
# homeassistant.components.dlink
|
||||
pyW215==0.8.0
|
||||
|
||||
37
tests/components/eurotronic_cometblue/test_button.py
Normal file
37
tests/components/eurotronic_cometblue/test_button.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Test the eurotronic_cometblue button platform."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .conftest import setup_with_selected_platforms
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_button_press_sync_time(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test that pressing the sync-time button sets the device datetime."""
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.BUTTON])
|
||||
|
||||
# Check if button is called correctly
|
||||
with patch.object(
|
||||
mock_config_entry.runtime_data.device,
|
||||
"set_datetime_async",
|
||||
) as mock_set_datetime:
|
||||
await hass.services.async_call(
|
||||
BUTTON_DOMAIN,
|
||||
"press",
|
||||
{ATTR_ENTITY_ID: "button.comet_blue_aa_bb_cc_dd_ee_ff_sync_time"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_set_datetime.assert_called_once_with(date=dt_util.now())
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Tests for the Luftdaten integration."""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from luftdaten.exceptions import LuftdatenError
|
||||
from luftdaten.exceptions import LuftdatenConnectionError, LuftdatenError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.luftdaten.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
@@ -14,7 +15,7 @@ from tests.common import MockConfigEntry
|
||||
async def test_load_unload_config_entry(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_luftdaten: AsyncMock,
|
||||
mock_luftdaten: MagicMock,
|
||||
) -> None:
|
||||
"""Test the Luftdaten configuration entry loading/unloading."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
@@ -30,21 +31,21 @@ async def test_load_unload_config_entry(
|
||||
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
@patch(
|
||||
"homeassistant.components.luftdaten.Luftdaten.get_data",
|
||||
side_effect=LuftdatenError,
|
||||
)
|
||||
@pytest.mark.parametrize("side_effect", [LuftdatenConnectionError, LuftdatenError])
|
||||
async def test_config_entry_not_ready(
|
||||
mock_get_data: MagicMock,
|
||||
hass: HomeAssistant,
|
||||
mock_luftdaten: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
side_effect: type[Exception],
|
||||
) -> None:
|
||||
"""Test the Luftdaten configuration entry not ready."""
|
||||
mock_luftdaten.get_data.side_effect = side_effect
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_get_data.call_count == 1
|
||||
assert mock_luftdaten.get_data.call_count == 1
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ from homeassistant.components.mastodon.const import (
|
||||
ATTR_MEDIA,
|
||||
ATTR_MEDIA_DESCRIPTION,
|
||||
ATTR_NOTE,
|
||||
ATTR_QUOTE_APPROVAL_POLICY,
|
||||
ATTR_STATUS,
|
||||
ATTR_VISIBILITY,
|
||||
DOMAIN,
|
||||
@@ -378,6 +379,7 @@ async def test_unmute_account_failure_api_error(
|
||||
"status": "test toot",
|
||||
"spoiler_text": None,
|
||||
"visibility": None,
|
||||
"quote_approval_policy": None,
|
||||
"idempotency_key": None,
|
||||
"language": None,
|
||||
"media_ids": None,
|
||||
@@ -390,6 +392,7 @@ async def test_unmute_account_failure_api_error(
|
||||
"status": "test toot",
|
||||
"spoiler_text": None,
|
||||
"visibility": "private",
|
||||
"quote_approval_policy": None,
|
||||
"idempotency_key": None,
|
||||
"language": None,
|
||||
"media_ids": None,
|
||||
@@ -406,6 +409,7 @@ async def test_unmute_account_failure_api_error(
|
||||
"status": "test toot",
|
||||
"spoiler_text": "Spoiler",
|
||||
"visibility": "private",
|
||||
"quote_approval_policy": None,
|
||||
"idempotency_key": None,
|
||||
"language": None,
|
||||
"media_ids": None,
|
||||
@@ -423,6 +427,7 @@ async def test_unmute_account_failure_api_error(
|
||||
"status": "test toot",
|
||||
"spoiler_text": "Spoiler",
|
||||
"visibility": None,
|
||||
"quote_approval_policy": None,
|
||||
"idempotency_key": None,
|
||||
"language": "nl",
|
||||
"media_ids": "1",
|
||||
@@ -441,6 +446,7 @@ async def test_unmute_account_failure_api_error(
|
||||
"status": "test toot",
|
||||
"spoiler_text": "Spoiler",
|
||||
"visibility": None,
|
||||
"quote_approval_policy": None,
|
||||
"idempotency_key": None,
|
||||
"language": "en",
|
||||
"media_ids": "1",
|
||||
@@ -454,6 +460,7 @@ async def test_unmute_account_failure_api_error(
|
||||
"language": "invalid-lang",
|
||||
"spoiler_text": None,
|
||||
"visibility": None,
|
||||
"quote_approval_policy": None,
|
||||
"idempotency_key": None,
|
||||
"media_ids": None,
|
||||
"sensitive": None,
|
||||
@@ -470,6 +477,20 @@ async def test_unmute_account_failure_api_error(
|
||||
"language": None,
|
||||
"spoiler_text": None,
|
||||
"visibility": None,
|
||||
"quote_approval_policy": None,
|
||||
"media_ids": None,
|
||||
"sensitive": None,
|
||||
},
|
||||
),
|
||||
(
|
||||
{ATTR_STATUS: "test toot", ATTR_QUOTE_APPROVAL_POLICY: "followers"},
|
||||
{
|
||||
"status": "test toot",
|
||||
"spoiler_text": None,
|
||||
"visibility": None,
|
||||
"quote_approval_policy": "followers",
|
||||
"idempotency_key": None,
|
||||
"language": None,
|
||||
"media_ids": None,
|
||||
"sensitive": None,
|
||||
},
|
||||
@@ -528,6 +549,7 @@ async def test_service_post(
|
||||
"status": "test toot",
|
||||
"spoiler_text": "Spoiler",
|
||||
"visibility": None,
|
||||
"quote_approval_policy": None,
|
||||
"idempotency_key": None,
|
||||
"media_ids": "1",
|
||||
"media_description": None,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"""Tests for the RDW integration."""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from vehicle import RDWConnectionError, RDWError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -11,7 +14,7 @@ from tests.common import MockConfigEntry
|
||||
async def test_load_unload_config_entry(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_rdw: AsyncMock,
|
||||
mock_rdw: MagicMock,
|
||||
) -> None:
|
||||
"""Test the RDW configuration entry loading/unloading."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
@@ -26,19 +29,19 @@ async def test_load_unload_config_entry(
|
||||
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
@patch(
|
||||
"homeassistant.components.rdw.coordinator.RDW.vehicle",
|
||||
side_effect=RuntimeError,
|
||||
)
|
||||
@pytest.mark.parametrize("side_effect", [RDWConnectionError, RDWError])
|
||||
async def test_config_entry_not_ready(
|
||||
mock_request: MagicMock,
|
||||
hass: HomeAssistant,
|
||||
mock_rdw: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
side_effect: type[Exception],
|
||||
) -> None:
|
||||
"""Test the RDW configuration entry not ready."""
|
||||
mock_rdw.vehicle.side_effect = side_effect
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_request.call_count == 1
|
||||
assert mock_rdw.vehicle.call_count == 1
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
Reference in New Issue
Block a user