core/tests/components/esphome/test_entity.py

436 lines
14 KiB
Python
Raw Normal View History

"""Test ESPHome binary sensors."""
import asyncio
from collections.abc import Awaitable, Callable
from typing import Any
from unittest.mock import AsyncMock
from aioesphomeapi import (
APIClient,
BinarySensorInfo,
BinarySensorState,
EntityInfo,
EntityState,
SensorInfo,
SensorState,
UserService,
)
from homeassistant.const import (
ATTR_RESTORED,
EVENT_HOMEASSISTANT_STOP,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
)
from homeassistant.core import Event, EventStateChangedData, HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.event import async_track_state_change_event
from .conftest import MockESPHomeDevice
async def test_entities_removed(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_client: APIClient,
hass_storage: dict[str, Any],
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test entities are removed when static info changes."""
entity_info = [
BinarySensorInfo(
object_id="mybinary_sensor",
key=1,
name="my binary_sensor",
unique_id="my_binary_sensor",
),
BinarySensorInfo(
object_id="mybinary_sensor_to_be_removed",
key=2,
name="my binary_sensor to be removed",
unique_id="mybinary_sensor_to_be_removed",
),
]
states = [
BinarySensorState(key=1, state=True, missing_state=False),
BinarySensorState(key=2, state=True, missing_state=False),
]
user_service = []
mock_device = await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
)
entry = mock_device.entry
entry_id = entry.entry_id
storage_key = f"esphome.{entry_id}"
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_ON
state = hass.states.get("binary_sensor.test_mybinary_sensor_to_be_removed")
assert state is not None
assert state.state == STATE_ON
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert len(hass_storage[storage_key]["data"]["binary_sensor"]) == 2
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.attributes[ATTR_RESTORED] is True
state = hass.states.get("binary_sensor.test_mybinary_sensor_to_be_removed")
assert state is not None
reg_entry = entity_registry.async_get(
"binary_sensor.test_mybinary_sensor_to_be_removed"
)
assert reg_entry is not None
assert state.attributes[ATTR_RESTORED] is True
entity_info = [
BinarySensorInfo(
object_id="mybinary_sensor",
key=1,
name="my binary_sensor",
unique_id="my_binary_sensor",
),
]
states = [
BinarySensorState(key=1, state=True, missing_state=False),
]
mock_device = await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
entry=entry,
)
assert mock_device.entry.entry_id == entry_id
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_ON
state = hass.states.get("binary_sensor.test_mybinary_sensor_to_be_removed")
assert state is None
reg_entry = entity_registry.async_get(
"binary_sensor.test_mybinary_sensor_to_be_removed"
)
assert reg_entry is None
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert len(hass_storage[storage_key]["data"]["binary_sensor"]) == 1
async def test_entities_removed_after_reload(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_client: APIClient,
hass_storage: dict[str, Any],
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test entities and their registry entry are removed when static info changes after a reload."""
entity_info = [
BinarySensorInfo(
object_id="mybinary_sensor",
key=1,
name="my binary_sensor",
unique_id="my_binary_sensor",
),
BinarySensorInfo(
object_id="mybinary_sensor_to_be_removed",
key=2,
name="my binary_sensor to be removed",
unique_id="mybinary_sensor_to_be_removed",
),
]
states = [
BinarySensorState(key=1, state=True, missing_state=False),
BinarySensorState(key=2, state=True, missing_state=False),
]
user_service = []
mock_device: MockESPHomeDevice = await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
)
entry = mock_device.entry
entry_id = entry.entry_id
storage_key = f"esphome.{entry_id}"
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_ON
state = hass.states.get("binary_sensor.test_mybinary_sensor_to_be_removed")
assert state is not None
assert state.state == STATE_ON
reg_entry = entity_registry.async_get(
"binary_sensor.test_mybinary_sensor_to_be_removed"
)
assert reg_entry is not None
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert len(hass_storage[storage_key]["data"]["binary_sensor"]) == 2
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.attributes[ATTR_RESTORED] is True
state = hass.states.get("binary_sensor.test_mybinary_sensor_to_be_removed")
assert state is not None
assert state.attributes[ATTR_RESTORED] is True
reg_entry = entity_registry.async_get(
"binary_sensor.test_mybinary_sensor_to_be_removed"
)
assert reg_entry is not None
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert len(hass_storage[storage_key]["data"]["binary_sensor"]) == 2
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert ATTR_RESTORED not in state.attributes
state = hass.states.get("binary_sensor.test_mybinary_sensor_to_be_removed")
assert state is not None
assert ATTR_RESTORED not in state.attributes
reg_entry = entity_registry.async_get(
"binary_sensor.test_mybinary_sensor_to_be_removed"
)
assert reg_entry is not None
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
entity_info = [
BinarySensorInfo(
object_id="mybinary_sensor",
key=1,
name="my binary_sensor",
unique_id="my_binary_sensor",
),
]
states = [
BinarySensorState(key=1, state=True, missing_state=False),
]
mock_device.client.list_entities_services = AsyncMock(
return_value=(entity_info, user_service)
)
assert await hass.config_entries.async_setup(entry.entry_id)
on_future = hass.loop.create_future()
@callback
def _async_wait_for_on(event: Event[EventStateChangedData]) -> None:
if event.data["new_state"].state == STATE_ON:
on_future.set_result(None)
async_track_state_change_event(
hass, ["binary_sensor.test_mybinary_sensor"], _async_wait_for_on
)
await hass.async_block_till_done()
async with asyncio.timeout(2):
await on_future
assert mock_device.entry.entry_id == entry_id
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_ON
state = hass.states.get("binary_sensor.test_mybinary_sensor_to_be_removed")
assert state is None
await hass.async_block_till_done()
reg_entry = entity_registry.async_get(
"binary_sensor.test_mybinary_sensor_to_be_removed"
)
assert reg_entry is None
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert len(hass_storage[storage_key]["data"]["binary_sensor"]) == 1
async def test_entity_info_object_ids(
hass: HomeAssistant,
mock_client: APIClient,
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test how object ids affect entity id."""
entity_info = [
BinarySensorInfo(
object_id="object_id_is_used",
key=1,
name="my binary_sensor",
unique_id="my_binary_sensor",
)
]
states = []
user_service = []
await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
)
state = hass.states.get("binary_sensor.test_object_id_is_used")
assert state is not None
async def test_deep_sleep_device(
hass: HomeAssistant,
mock_client: APIClient,
hass_storage: dict[str, Any],
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test a deep sleep device."""
entity_info = [
BinarySensorInfo(
object_id="mybinary_sensor",
key=1,
name="my binary_sensor",
unique_id="my_binary_sensor",
),
SensorInfo(
object_id="my_sensor",
key=3,
name="my sensor",
unique_id="my_sensor",
),
]
states = [
BinarySensorState(key=1, state=True, missing_state=False),
BinarySensorState(key=2, state=True, missing_state=False),
SensorState(key=3, state=123.0, missing_state=False),
]
user_service = []
mock_device = await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
device_info={"has_deep_sleep": True},
)
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_ON
state = hass.states.get("sensor.test_my_sensor")
assert state is not None
assert state.state == "123"
await mock_device.mock_disconnect(False)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_UNAVAILABLE
state = hass.states.get("sensor.test_my_sensor")
assert state is not None
assert state.state == STATE_UNAVAILABLE
await mock_device.mock_connect()
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_ON
state = hass.states.get("sensor.test_my_sensor")
assert state is not None
assert state.state == "123"
await mock_device.mock_disconnect(True)
await hass.async_block_till_done()
await mock_device.mock_connect()
await hass.async_block_till_done()
mock_device.set_state(BinarySensorState(key=1, state=False, missing_state=False))
mock_device.set_state(SensorState(key=3, state=56, missing_state=False))
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_OFF
state = hass.states.get("sensor.test_my_sensor")
assert state is not None
assert state.state == "56"
await mock_device.mock_disconnect(True)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_OFF
state = hass.states.get("sensor.test_my_sensor")
assert state is not None
assert state.state == "56"
await mock_device.mock_connect()
await hass.async_block_till_done()
await mock_device.mock_disconnect(False)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_UNAVAILABLE
state = hass.states.get("sensor.test_my_sensor")
assert state is not None
assert state.state == STATE_UNAVAILABLE
await mock_device.mock_connect()
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_ON
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
# Verify we do not dispatch any more state updates or
# availability updates after the stop event is fired
state = hass.states.get("binary_sensor.test_mybinary_sensor")
assert state is not None
assert state.state == STATE_ON
async def test_esphome_device_without_friendly_name(
hass: HomeAssistant,
mock_client: APIClient,
hass_storage: dict[str, Any],
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test a device without friendly_name set."""
entity_info = [
BinarySensorInfo(
object_id="mybinary_sensor",
key=1,
name="my binary_sensor",
unique_id="my_binary_sensor",
),
]
states = [
BinarySensorState(key=1, state=True, missing_state=False),
BinarySensorState(key=2, state=True, missing_state=False),
]
user_service = []
await mock_esphome_device(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
device_info={"friendly_name": None},
)
state = hass.states.get("binary_sensor.my_binary_sensor")
assert state is not None
assert state.state == STATE_ON