Fix esphome binary sensors when state is missing (#95140)

* Fix esphome binary sensors when state is missing

* Fix esphome binary sensors when state is missing

* Fix esphome binary sensors when state is missing
This commit is contained in:
J. Nick Koston 2023-06-24 22:09:26 -05:00 committed by GitHub
parent c6b3d538de
commit 9eedc8a602
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 125 additions and 16 deletions

View File

@ -49,10 +49,9 @@ class EsphomeBinarySensor(
# Status binary sensors indicated connected state.
# So in their case what's usually _availability_ is now state
return self._entry_data.available
state = self._state
if not self._has_state or state.missing_state:
if not self._has_state or self._state.missing_state:
return None
return state.state
return self._state.state
@callback
def _on_static_info_update(self, static_info: EntityInfo) -> None:

View File

@ -2,7 +2,7 @@
from __future__ import annotations
from asyncio import Event
from collections.abc import Callable
from collections.abc import Awaitable, Callable
from typing import Any
from unittest.mock import AsyncMock, Mock, patch
@ -142,13 +142,30 @@ async def mock_dashboard(hass):
yield data
class MockESPHomeDevice:
"""Mock an esphome device."""
def __init__(self, entry: MockConfigEntry) -> None:
"""Init the mock."""
self.entry = entry
self.state_callback: Callable[[EntityState], None]
def set_state_callback(self, state_callback: Callable[[EntityState], None]) -> None:
"""Set the state callback."""
self.state_callback = state_callback
def set_state(self, state: EntityState) -> None:
"""Mock setting state."""
self.state_callback(state)
async def _mock_generic_device_entry(
hass: HomeAssistant,
mock_client: APIClient,
mock_device_info: dict[str, Any],
mock_list_entities_services: tuple[list[EntityInfo], list[UserService]],
states: list[EntityState],
) -> MockConfigEntry:
) -> MockESPHomeDevice:
entry = MockConfigEntry(
domain=DOMAIN,
data={
@ -158,6 +175,7 @@ async def _mock_generic_device_entry(
},
)
entry.add_to_hass(hass)
mock_device = MockESPHomeDevice(entry)
device_info = DeviceInfo(
name="test",
@ -169,6 +187,7 @@ async def _mock_generic_device_entry(
async def _subscribe_states(callback: Callable[[EntityState], None]) -> None:
"""Subscribe to state."""
mock_device.set_state_callback(callback)
for state in states:
callback(state)
@ -194,7 +213,7 @@ async def _mock_generic_device_entry(
await hass.async_block_till_done()
return entry
return mock_device
@pytest.fixture
@ -205,9 +224,11 @@ async def mock_voice_assistant_entry(
"""Set up an ESPHome entry with voice assistant."""
async def _mock_voice_assistant_entry(version: int) -> MockConfigEntry:
return await _mock_generic_device_entry(
hass, mock_client, {"voice_assistant_version": version}, ([], []), []
)
return (
await _mock_generic_device_entry(
hass, mock_client, {"voice_assistant_version": version}, ([], []), []
)
).entry
return _mock_voice_assistant_entry
@ -227,8 +248,11 @@ async def mock_voice_assistant_v2_entry(mock_voice_assistant_entry) -> MockConfi
@pytest.fixture
async def mock_generic_device_entry(
hass: HomeAssistant,
) -> MockConfigEntry:
"""Set up an ESPHome entry."""
) -> Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockConfigEntry],
]:
"""Set up an ESPHome entry and return the MockConfigEntry."""
async def _mock_device_entry(
mock_client: APIClient,
@ -236,8 +260,32 @@ async def mock_generic_device_entry(
user_service: list[UserService],
states: list[EntityState],
) -> MockConfigEntry:
return (
await _mock_generic_device_entry(
hass, mock_client, {}, (entity_info, user_service), states
)
).entry
return _mock_device_entry
@pytest.fixture
async def mock_esphome_device(
hass: HomeAssistant,
) -> Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
]:
"""Set up an ESPHome entry and return the MockESPHomeDevice."""
async def _mock_device(
mock_client: APIClient,
entity_info: list[EntityInfo],
user_service: list[UserService],
states: list[EntityState],
) -> MockESPHomeDevice:
return await _mock_generic_device_entry(
hass, mock_client, {}, (entity_info, user_service), states
)
return _mock_device_entry
return _mock_device

View File

@ -1,11 +1,24 @@
"""Test ESPHome binary sensors."""
from aioesphomeapi import APIClient, BinarySensorInfo, BinarySensorState
from collections.abc import Awaitable, Callable
from aioesphomeapi import (
APIClient,
BinarySensorInfo,
BinarySensorState,
EntityInfo,
EntityState,
UserService,
)
import pytest
from homeassistant.components.esphome import DomainData
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from .conftest import MockESPHomeDevice
from tests.common import MockConfigEntry
async def test_assist_in_progress(
hass: HomeAssistant,
@ -37,7 +50,10 @@ async def test_binary_sensor_generic_entity(
hass: HomeAssistant,
mock_client: APIClient,
binary_state: tuple[bool, str],
mock_generic_device_entry,
mock_generic_device_entry: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockConfigEntry],
],
) -> None:
"""Test a generic binary_sensor entity."""
entity_info = [
@ -63,7 +79,12 @@ async def test_binary_sensor_generic_entity(
async def test_status_binary_sensor(
hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
hass: HomeAssistant,
mock_client: APIClient,
mock_generic_device_entry: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockConfigEntry],
],
) -> None:
"""Test a generic binary_sensor entity."""
entity_info = [
@ -89,7 +110,12 @@ async def test_status_binary_sensor(
async def test_binary_sensor_missing_state(
hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
hass: HomeAssistant,
mock_client: APIClient,
mock_generic_device_entry: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockConfigEntry],
],
) -> None:
"""Test a generic binary_sensor that is missing state."""
entity_info = [
@ -111,3 +137,39 @@ async def test_binary_sensor_missing_state(
state = hass.states.get("binary_sensor.test_my_binary_sensor")
assert state is not None
assert state.state == STATE_UNKNOWN
async def test_binary_sensor_has_state_false(
hass: HomeAssistant,
mock_client: APIClient,
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test a generic binary_sensor where has_state is false."""
entity_info = [
BinarySensorInfo(
object_id="mybinary_sensor",
key=1,
name="my binary_sensor",
unique_id="my_binary_sensor",
)
]
states = []
user_service = []
mock_device = 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_my_binary_sensor")
assert state is not None
assert state.state == STATE_UNKNOWN
mock_device.set_state(BinarySensorState(key=1, state=True, missing_state=False))
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.test_my_binary_sensor")
assert state is not None
assert state.state == STATE_ON