mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
Add event entity (#96797)
This commit is contained in:
parent
4916351d9a
commit
747f4d4a73
@ -24,6 +24,7 @@ base_platforms: &base_platforms
|
||||
- homeassistant/components/datetime/**
|
||||
- homeassistant/components/device_tracker/**
|
||||
- homeassistant/components/diagnostics/**
|
||||
- homeassistant/components/event/**
|
||||
- homeassistant/components/fan/**
|
||||
- homeassistant/components/geo_location/**
|
||||
- homeassistant/components/humidifier/**
|
||||
|
@ -113,6 +113,7 @@ homeassistant.components.elkm1.*
|
||||
homeassistant.components.emulated_hue.*
|
||||
homeassistant.components.energy.*
|
||||
homeassistant.components.esphome.*
|
||||
homeassistant.components.event.*
|
||||
homeassistant.components.evil_genius_labs.*
|
||||
homeassistant.components.fan.*
|
||||
homeassistant.components.fastdotcom.*
|
||||
|
@ -358,6 +358,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/esphome/ @OttoWinter @jesserockz @bdraco
|
||||
/homeassistant/components/eufylife_ble/ @bdr99
|
||||
/tests/components/eufylife_ble/ @bdr99
|
||||
/homeassistant/components/event/ @home-assistant/core
|
||||
/tests/components/event/ @home-assistant/core
|
||||
/homeassistant/components/evil_genius_labs/ @balloob
|
||||
/tests/components/evil_genius_labs/ @balloob
|
||||
/homeassistant/components/evohome/ @zxdavb
|
||||
|
@ -30,6 +30,7 @@ COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM = [
|
||||
Platform.COVER,
|
||||
Platform.DATE,
|
||||
Platform.DATETIME,
|
||||
Platform.EVENT,
|
||||
Platform.FAN,
|
||||
Platform.HUMIDIFIER,
|
||||
Platform.LIGHT,
|
||||
|
@ -51,3 +51,4 @@ class DemoButton(ButtonEntity):
|
||||
persistent_notification.async_create(
|
||||
self.hass, "Button pressed", title="Button"
|
||||
)
|
||||
self.hass.bus.async_fire("demo_button_pressed")
|
||||
|
47
homeassistant/components/demo/event.py
Normal file
47
homeassistant/components/demo/event.py
Normal file
@ -0,0 +1,47 @@
|
||||
"""Demo platform that offers a fake event entity."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.event import EventDeviceClass, EventEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the demo event platform."""
|
||||
async_add_entities([DemoEvent()])
|
||||
|
||||
|
||||
class DemoEvent(EventEntity):
|
||||
"""Representation of a demo event entity."""
|
||||
|
||||
_attr_device_class = EventDeviceClass.BUTTON
|
||||
_attr_event_types = ["pressed"]
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = "Button press"
|
||||
_attr_should_poll = False
|
||||
_attr_translation_key = "push"
|
||||
_attr_unique_id = "push"
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the Demo event entity."""
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, "push")},
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
self.hass.bus.async_listen("demo_button_pressed", self._async_handle_event)
|
||||
|
||||
@callback
|
||||
def _async_handle_event(self, _: Event) -> None:
|
||||
"""Handle the demo button event."""
|
||||
self._trigger_event("pressed")
|
||||
self.async_write_ha_state()
|
@ -46,6 +46,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"event": {
|
||||
"push": {
|
||||
"state_attributes": {
|
||||
"event_type": {
|
||||
"state": {
|
||||
"pressed": "Pressed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"speed": {
|
||||
"state": {
|
||||
|
209
homeassistant/components/event/__init__.py
Normal file
209
homeassistant/components/event/__init__.py
Normal file
@ -0,0 +1,209 @@
|
||||
"""Component for handling incoming events as a platform."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any, final
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
PLATFORM_SCHEMA,
|
||||
PLATFORM_SCHEMA_BASE,
|
||||
)
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import ATTR_EVENT_TYPE, ATTR_EVENT_TYPES, DOMAIN
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EventDeviceClass(StrEnum):
|
||||
"""Device class for events."""
|
||||
|
||||
DOORBELL = "doorbell"
|
||||
BUTTON = "button"
|
||||
MOTION = "motion"
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ATTR_EVENT_TYPE",
|
||||
"ATTR_EVENT_TYPES",
|
||||
"DOMAIN",
|
||||
"PLATFORM_SCHEMA_BASE",
|
||||
"PLATFORM_SCHEMA",
|
||||
"EventDeviceClass",
|
||||
"EventEntity",
|
||||
"EventEntityDescription",
|
||||
"EventEntityFeature",
|
||||
]
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up Event entities."""
|
||||
component = hass.data[DOMAIN] = EntityComponent[EventEntity](
|
||||
_LOGGER, DOMAIN, hass, SCAN_INTERVAL
|
||||
)
|
||||
await component.async_setup(config)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up a config entry."""
|
||||
component: EntityComponent[EventEntity] = hass.data[DOMAIN]
|
||||
return await component.async_setup_entry(entry)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
component: EntityComponent[EventEntity] = hass.data[DOMAIN]
|
||||
return await component.async_unload_entry(entry)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EventEntityDescription(EntityDescription):
|
||||
"""A class that describes event entities."""
|
||||
|
||||
device_class: EventDeviceClass | None = None
|
||||
event_types: list[str] | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class EventExtraStoredData(ExtraStoredData):
|
||||
"""Object to hold extra stored data."""
|
||||
|
||||
last_event_type: str | None
|
||||
last_event_attributes: dict[str, Any] | None
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
"""Return a dict representation of the event data."""
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, restored: dict[str, Any]) -> Self | None:
|
||||
"""Initialize a stored event state from a dict."""
|
||||
try:
|
||||
return cls(
|
||||
restored["last_event_type"],
|
||||
restored["last_event_attributes"],
|
||||
)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
class EventEntity(RestoreEntity):
|
||||
"""Representation of a Event entity."""
|
||||
|
||||
entity_description: EventEntityDescription
|
||||
_attr_device_class: EventDeviceClass | None
|
||||
_attr_event_types: list[str]
|
||||
_attr_state: None
|
||||
|
||||
__last_event_triggered: datetime | None = None
|
||||
__last_event_type: str | None = None
|
||||
__last_event_attributes: dict[str, Any] | None = None
|
||||
|
||||
@property
|
||||
def device_class(self) -> EventDeviceClass | None:
|
||||
"""Return the class of this entity."""
|
||||
if hasattr(self, "_attr_device_class"):
|
||||
return self._attr_device_class
|
||||
if hasattr(self, "entity_description"):
|
||||
return self.entity_description.device_class
|
||||
return None
|
||||
|
||||
@property
|
||||
def event_types(self) -> list[str]:
|
||||
"""Return a list of possible events."""
|
||||
if hasattr(self, "_attr_event_types"):
|
||||
return self._attr_event_types
|
||||
if (
|
||||
hasattr(self, "entity_description")
|
||||
and self.entity_description.event_types is not None
|
||||
):
|
||||
return self.entity_description.event_types
|
||||
raise AttributeError()
|
||||
|
||||
@final
|
||||
def _trigger_event(
|
||||
self, event_type: str, event_attributes: dict[str, Any] | None = None
|
||||
) -> None:
|
||||
"""Process a new event."""
|
||||
if event_type not in self.event_types:
|
||||
raise ValueError(f"Invalid event type {event_type} for {self.entity_id}")
|
||||
self.__last_event_triggered = dt_util.utcnow()
|
||||
self.__last_event_type = event_type
|
||||
self.__last_event_attributes = event_attributes
|
||||
|
||||
def _default_to_device_class_name(self) -> bool:
|
||||
"""Return True if an unnamed entity should be named by its device class.
|
||||
|
||||
For events this is True if the entity has a device class.
|
||||
"""
|
||||
return self.device_class is not None
|
||||
|
||||
@property
|
||||
@final
|
||||
def capability_attributes(self) -> dict[str, list[str]]:
|
||||
"""Return capability attributes."""
|
||||
return {
|
||||
ATTR_EVENT_TYPES: self.event_types,
|
||||
}
|
||||
|
||||
@property
|
||||
@final
|
||||
def state(self) -> str | None:
|
||||
"""Return the entity state."""
|
||||
if (last_event := self.__last_event_triggered) is None:
|
||||
return None
|
||||
return last_event.isoformat(timespec="milliseconds")
|
||||
|
||||
@final
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes."""
|
||||
attributes = {ATTR_EVENT_TYPE: self.__last_event_type}
|
||||
if self.__last_event_attributes:
|
||||
attributes |= self.__last_event_attributes
|
||||
return attributes
|
||||
|
||||
@final
|
||||
async def async_internal_added_to_hass(self) -> None:
|
||||
"""Call when the event entity is added to hass."""
|
||||
await super().async_internal_added_to_hass()
|
||||
if (
|
||||
(state := await self.async_get_last_state())
|
||||
and state.state is not None
|
||||
and (event_data := await self.async_get_last_event_data())
|
||||
):
|
||||
self.__last_event_triggered = dt_util.parse_datetime(state.state)
|
||||
self.__last_event_type = event_data.last_event_type
|
||||
self.__last_event_attributes = event_data.last_event_attributes
|
||||
|
||||
@property
|
||||
def extra_restore_state_data(self) -> EventExtraStoredData:
|
||||
"""Return event specific state data to be restored."""
|
||||
return EventExtraStoredData(
|
||||
self.__last_event_type,
|
||||
self.__last_event_attributes,
|
||||
)
|
||||
|
||||
async def async_get_last_event_data(self) -> EventExtraStoredData | None:
|
||||
"""Restore event specific state date."""
|
||||
if (restored_last_extra_data := await self.async_get_last_extra_data()) is None:
|
||||
return None
|
||||
return EventExtraStoredData.from_dict(restored_last_extra_data.as_dict())
|
5
homeassistant/components/event/const.py
Normal file
5
homeassistant/components/event/const.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Provides the constants needed for the component."""
|
||||
|
||||
DOMAIN = "event"
|
||||
ATTR_EVENT_TYPE = "event_type"
|
||||
ATTR_EVENT_TYPES = "event_types"
|
8
homeassistant/components/event/manifest.json
Normal file
8
homeassistant/components/event/manifest.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"domain": "event",
|
||||
"name": "Event",
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/event",
|
||||
"integration_type": "entity",
|
||||
"quality_scale": "internal"
|
||||
}
|
12
homeassistant/components/event/recorder.py
Normal file
12
homeassistant/components/event/recorder.py
Normal file
@ -0,0 +1,12 @@
|
||||
"""Integration platform for recorder."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from . import ATTR_EVENT_TYPES
|
||||
|
||||
|
||||
@callback
|
||||
def exclude_attributes(hass: HomeAssistant) -> set[str]:
|
||||
"""Exclude static attributes from being recorded in the database."""
|
||||
return {ATTR_EVENT_TYPES}
|
25
homeassistant/components/event/strings.json
Normal file
25
homeassistant/components/event/strings.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"title": "Event",
|
||||
"entity_component": {
|
||||
"_": {
|
||||
"name": "[%key:component::button::title%]",
|
||||
"state_attributes": {
|
||||
"event_type": {
|
||||
"name": "Event type"
|
||||
},
|
||||
"event_types": {
|
||||
"name": "Event types"
|
||||
}
|
||||
}
|
||||
},
|
||||
"doorbell": {
|
||||
"name": "Doorbell"
|
||||
},
|
||||
"button": {
|
||||
"name": "Button"
|
||||
},
|
||||
"motion": {
|
||||
"name": "Motion"
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ class Platform(StrEnum):
|
||||
DATE = "date"
|
||||
DATETIME = "datetime"
|
||||
DEVICE_TRACKER = "device_tracker"
|
||||
EVENT = "event"
|
||||
FAN = "fan"
|
||||
GEO_LOCATION = "geo_location"
|
||||
HUMIDIFIER = "humidifier"
|
||||
|
10
mypy.ini
10
mypy.ini
@ -892,6 +892,16 @@ disallow_untyped_defs = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.event.*]
|
||||
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.evil_genius_labs.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
1
tests/components/event/__init__.py
Normal file
1
tests/components/event/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""The tests for the event integration."""
|
352
tests/components/event/test_init.py
Normal file
352
tests/components/event/test_init.py
Normal file
@ -0,0 +1,352 @@
|
||||
"""The tests for the event integration."""
|
||||
from collections.abc import Generator
|
||||
from typing import Any
|
||||
|
||||
from freezegun import freeze_time
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.event import (
|
||||
ATTR_EVENT_TYPE,
|
||||
ATTR_EVENT_TYPES,
|
||||
DOMAIN,
|
||||
EventDeviceClass,
|
||||
EventEntity,
|
||||
EventEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow
|
||||
from homeassistant.const import CONF_PLATFORM, STATE_UNKNOWN
|
||||
from homeassistant.core import HomeAssistant, State
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import STORAGE_KEY as RESTORE_STATE_KEY
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
MockModule,
|
||||
MockPlatform,
|
||||
async_mock_restore_state_shutdown_restart,
|
||||
mock_config_flow,
|
||||
mock_integration,
|
||||
mock_platform,
|
||||
mock_restore_cache,
|
||||
mock_restore_cache_with_extra_data,
|
||||
)
|
||||
|
||||
TEST_DOMAIN = "test"
|
||||
|
||||
|
||||
async def test_event() -> None:
|
||||
"""Test the event entity."""
|
||||
event = EventEntity()
|
||||
event.entity_id = "event.doorbell"
|
||||
# Test event with no data at all
|
||||
assert event.state is None
|
||||
assert event.state_attributes == {ATTR_EVENT_TYPE: None}
|
||||
assert not event.extra_state_attributes
|
||||
assert event.device_class is None
|
||||
|
||||
# No event types defined, should raise
|
||||
with pytest.raises(AttributeError):
|
||||
event.event_types
|
||||
|
||||
# Test retrieving data from entity description
|
||||
event.entity_description = EventEntityDescription(
|
||||
key="test_event",
|
||||
event_types=["short_press", "long_press"],
|
||||
device_class=EventDeviceClass.DOORBELL,
|
||||
)
|
||||
assert event.event_types == ["short_press", "long_press"]
|
||||
assert event.device_class == EventDeviceClass.DOORBELL
|
||||
|
||||
# Test attrs win over entity description
|
||||
event._attr_event_types = ["short_press", "long_press", "double_press"]
|
||||
assert event.event_types == ["short_press", "long_press", "double_press"]
|
||||
event._attr_device_class = EventDeviceClass.BUTTON
|
||||
assert event.device_class == EventDeviceClass.BUTTON
|
||||
|
||||
# Test triggering an event
|
||||
now = dt_util.utcnow()
|
||||
with freeze_time(now):
|
||||
event._trigger_event("long_press")
|
||||
|
||||
assert event.state == now.isoformat(timespec="milliseconds")
|
||||
assert event.state_attributes == {ATTR_EVENT_TYPE: "long_press"}
|
||||
assert not event.extra_state_attributes
|
||||
|
||||
# Test triggering an event, with extra attribute data
|
||||
now = dt_util.utcnow()
|
||||
with freeze_time(now):
|
||||
event._trigger_event("short_press", {"hello": "world"})
|
||||
|
||||
assert event.state == now.isoformat(timespec="milliseconds")
|
||||
assert event.state_attributes == {
|
||||
ATTR_EVENT_TYPE: "short_press",
|
||||
"hello": "world",
|
||||
}
|
||||
|
||||
# Test triggering an unknown event
|
||||
with pytest.raises(
|
||||
ValueError, match="^Invalid event type unknown_event for event.doorbell$"
|
||||
):
|
||||
event._trigger_event("unknown_event")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_custom_integrations")
|
||||
async def test_restore_state(hass: HomeAssistant) -> None:
|
||||
"""Test we restore state integration."""
|
||||
mock_restore_cache_with_extra_data(
|
||||
hass,
|
||||
(
|
||||
(
|
||||
State(
|
||||
"event.doorbell",
|
||||
"2021-01-01T23:59:59.123+00:00",
|
||||
attributes={
|
||||
ATTR_EVENT_TYPE: "ignored",
|
||||
ATTR_EVENT_TYPES: [
|
||||
"single_press",
|
||||
"double_press",
|
||||
"do",
|
||||
"not",
|
||||
"restore",
|
||||
],
|
||||
"hello": "worm",
|
||||
},
|
||||
),
|
||||
{
|
||||
"last_event_type": "double_press",
|
||||
"last_event_attributes": {
|
||||
"hello": "world",
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
platform.init()
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("event.doorbell")
|
||||
assert state
|
||||
assert state.state == "2021-01-01T23:59:59.123+00:00"
|
||||
assert state.attributes[ATTR_EVENT_TYPES] == ["short_press", "long_press"]
|
||||
assert state.attributes[ATTR_EVENT_TYPE] == "double_press"
|
||||
assert state.attributes["hello"] == "world"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_custom_integrations")
|
||||
async def test_invalid_extra_restore_state(hass: HomeAssistant) -> None:
|
||||
"""Test we restore state integration."""
|
||||
mock_restore_cache_with_extra_data(
|
||||
hass,
|
||||
(
|
||||
(
|
||||
State(
|
||||
"event.doorbell",
|
||||
"2021-01-01T23:59:59.123+00:00",
|
||||
),
|
||||
{
|
||||
"invalid_unexpected_key": "double_press",
|
||||
"last_event_attributes": {
|
||||
"hello": "world",
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
platform.init()
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("event.doorbell")
|
||||
assert state
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert state.attributes[ATTR_EVENT_TYPES] == ["short_press", "long_press"]
|
||||
assert state.attributes[ATTR_EVENT_TYPE] is None
|
||||
assert "hello" not in state.attributes
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_custom_integrations")
|
||||
async def test_no_extra_restore_state(hass: HomeAssistant) -> None:
|
||||
"""Test we restore state integration."""
|
||||
mock_restore_cache(
|
||||
hass,
|
||||
(
|
||||
State(
|
||||
"event.doorbell",
|
||||
"2021-01-01T23:59:59.123+00:00",
|
||||
attributes={
|
||||
ATTR_EVENT_TYPES: [
|
||||
"single_press",
|
||||
"double_press",
|
||||
],
|
||||
ATTR_EVENT_TYPE: "double_press",
|
||||
"hello": "world",
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
platform.init()
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("event.doorbell")
|
||||
assert state
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert state.attributes[ATTR_EVENT_TYPES] == ["short_press", "long_press"]
|
||||
assert state.attributes[ATTR_EVENT_TYPE] is None
|
||||
assert "hello" not in state.attributes
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("enable_custom_integrations")
|
||||
async def test_saving_state(hass: HomeAssistant, hass_storage: dict[str, Any]) -> None:
|
||||
"""Test we restore state integration."""
|
||||
restore_data = {"last_event_type": "double_press", "last_event_attributes": None}
|
||||
|
||||
mock_restore_cache_with_extra_data(
|
||||
hass,
|
||||
(
|
||||
(
|
||||
State(
|
||||
"event.doorbell",
|
||||
"2021-01-01T23:59:59.123+00:00",
|
||||
),
|
||||
restore_data,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
platform = getattr(hass.components, f"test.{DOMAIN}")
|
||||
platform.init()
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await async_mock_restore_state_shutdown_restart(hass)
|
||||
|
||||
assert len(hass_storage[RESTORE_STATE_KEY]["data"]) == 1
|
||||
state = hass_storage[RESTORE_STATE_KEY]["data"][0]["state"]
|
||||
assert state["entity_id"] == "event.doorbell"
|
||||
extra_data = hass_storage[RESTORE_STATE_KEY]["data"][0]["extra_data"]
|
||||
assert extra_data == restore_data
|
||||
|
||||
|
||||
class MockFlow(ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_flow_fixture(hass: HomeAssistant) -> Generator[None, None, None]:
|
||||
"""Mock config flow."""
|
||||
mock_platform(hass, f"{TEST_DOMAIN}.config_flow")
|
||||
|
||||
with mock_config_flow(TEST_DOMAIN, MockFlow):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("config_flow_fixture")
|
||||
async def test_name(hass: HomeAssistant) -> None:
|
||||
"""Test event name."""
|
||||
|
||||
async def async_setup_entry_init(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> bool:
|
||||
"""Set up test config entry."""
|
||||
await hass.config_entries.async_forward_entry_setup(config_entry, DOMAIN)
|
||||
return True
|
||||
|
||||
mock_platform(hass, f"{TEST_DOMAIN}.config_flow")
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule(
|
||||
TEST_DOMAIN,
|
||||
async_setup_entry=async_setup_entry_init,
|
||||
),
|
||||
)
|
||||
|
||||
# Unnamed event without device class -> no name
|
||||
entity1 = EventEntity()
|
||||
entity1._attr_event_types = ["ding"]
|
||||
entity1.entity_id = "event.test1"
|
||||
|
||||
# Unnamed event with device class but has_entity_name False -> no name
|
||||
entity2 = EventEntity()
|
||||
entity2._attr_event_types = ["ding"]
|
||||
entity2.entity_id = "event.test2"
|
||||
entity2._attr_device_class = EventDeviceClass.DOORBELL
|
||||
|
||||
# Unnamed event with device class and has_entity_name True -> named
|
||||
entity3 = EventEntity()
|
||||
entity3._attr_event_types = ["ding"]
|
||||
entity3.entity_id = "event.test3"
|
||||
entity3._attr_device_class = EventDeviceClass.DOORBELL
|
||||
entity3._attr_has_entity_name = True
|
||||
|
||||
# Unnamed event with device class and has_entity_name True -> named
|
||||
entity4 = EventEntity()
|
||||
entity4._attr_event_types = ["ding"]
|
||||
entity4.entity_id = "event.test4"
|
||||
entity4.entity_description = EventEntityDescription(
|
||||
"test",
|
||||
EventDeviceClass.DOORBELL,
|
||||
has_entity_name=True,
|
||||
)
|
||||
|
||||
async def async_setup_entry_platform(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up test event platform via config entry."""
|
||||
async_add_entities([entity1, entity2, entity3, entity4])
|
||||
|
||||
mock_platform(
|
||||
hass,
|
||||
f"{TEST_DOMAIN}.{DOMAIN}",
|
||||
MockPlatform(async_setup_entry=async_setup_entry_platform),
|
||||
)
|
||||
|
||||
config_entry = MockConfigEntry(domain=TEST_DOMAIN)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state
|
||||
assert state.attributes == {"event_types": ["ding"], "event_type": None}
|
||||
|
||||
state = hass.states.get(entity2.entity_id)
|
||||
assert state
|
||||
assert state.attributes == {
|
||||
"event_types": ["ding"],
|
||||
"event_type": None,
|
||||
"device_class": "doorbell",
|
||||
}
|
||||
|
||||
state = hass.states.get(entity3.entity_id)
|
||||
assert state
|
||||
assert state.attributes == {
|
||||
"event_types": ["ding"],
|
||||
"event_type": None,
|
||||
"device_class": "doorbell",
|
||||
"friendly_name": "Doorbell",
|
||||
}
|
||||
|
||||
state = hass.states.get(entity4.entity_id)
|
||||
assert state
|
||||
assert state.attributes == {
|
||||
"event_types": ["ding"],
|
||||
"event_type": None,
|
||||
"device_class": "doorbell",
|
||||
"friendly_name": "Doorbell",
|
||||
}
|
50
tests/components/event/test_recorder.py
Normal file
50
tests/components/event/test_recorder.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""The tests for event recorder."""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import select
|
||||
from homeassistant.components.event import ATTR_EVENT_TYPES
|
||||
from homeassistant.components.recorder import Recorder
|
||||
from homeassistant.components.recorder.history import get_significant_states
|
||||
from homeassistant.const import ATTR_FRIENDLY_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.components.recorder.common import async_wait_recording_done
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def event_only() -> None:
|
||||
"""Enable only the event platform."""
|
||||
with patch(
|
||||
"homeassistant.components.demo.COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM",
|
||||
[Platform.EVENT],
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
async def test_exclude_attributes(recorder_mock: Recorder, hass: HomeAssistant) -> None:
|
||||
"""Test select registered attributes to be excluded."""
|
||||
now = dt_util.utcnow()
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
await async_setup_component(
|
||||
hass, select.DOMAIN, {select.DOMAIN: {"platform": "demo"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
hass.bus.async_fire("demo_button_pressed")
|
||||
await hass.async_block_till_done()
|
||||
await async_wait_recording_done(hass)
|
||||
|
||||
states = await hass.async_add_executor_job(
|
||||
get_significant_states, hass, now, None, hass.states.async_entity_ids()
|
||||
)
|
||||
assert len(states) >= 1
|
||||
for entity_states in states.values():
|
||||
for state in entity_states:
|
||||
assert state
|
||||
assert ATTR_EVENT_TYPES not in state.attributes
|
||||
assert ATTR_FRIENDLY_NAME in state.attributes
|
42
tests/testing_config/custom_components/test/event.py
Normal file
42
tests/testing_config/custom_components/test/event.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""Provide a mock event platform.
|
||||
|
||||
Call init before using it in your tests to ensure clean test data.
|
||||
"""
|
||||
from homeassistant.components.event import EventEntity
|
||||
|
||||
from tests.common import MockEntity
|
||||
|
||||
ENTITIES = []
|
||||
|
||||
|
||||
class MockEventEntity(MockEntity, EventEntity):
|
||||
"""Mock EventEntity class."""
|
||||
|
||||
@property
|
||||
def event_types(self) -> list[str]:
|
||||
"""Return a list of possible events."""
|
||||
return self._handle("event_types")
|
||||
|
||||
|
||||
def init(empty=False):
|
||||
"""Initialize the platform with entities."""
|
||||
global ENTITIES
|
||||
|
||||
ENTITIES = (
|
||||
[]
|
||||
if empty
|
||||
else [
|
||||
MockEventEntity(
|
||||
name="doorbell",
|
||||
unique_id="unique_doorbell",
|
||||
event_types=["short_press", "long_press"],
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities_callback, discovery_info=None
|
||||
):
|
||||
"""Return mock entities."""
|
||||
async_add_entities_callback(ENTITIES)
|
Loading…
x
Reference in New Issue
Block a user