Refactor Presence Feature

This commit is contained in:
Jean-Marc Collin
2024-12-23 12:04:22 +00:00
parent 6e5e304b71
commit b41d0f34dc
13 changed files with 618 additions and 263 deletions

View File

@@ -1,4 +1,4 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, abstract-method, too-many-lines, redefined-builtin
# pylint: disable=wildcard-import, unused-wildcard-import, unused-import, protected-access, unused-argument, line-too-long, abstract-method, too-many-lines, redefined-builtin
""" Some common resources """
import asyncio
@@ -8,7 +8,16 @@ from unittest.mock import patch, MagicMock # pylint: disable=unused-import
import pytest # pylint: disable=unused-import
from homeassistant.core import HomeAssistant, Event, EVENT_STATE_CHANGED, State
from homeassistant.const import UnitOfTemperature, STATE_ON, STATE_OFF, ATTR_TEMPERATURE
from homeassistant.const import (
UnitOfTemperature,
STATE_ON,
STATE_OFF,
ATTR_TEMPERATURE,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
STATE_HOME,
STATE_NOT_HOME,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.helpers.entity import Entity
@@ -837,7 +846,7 @@ async def send_motion_change_event(
async def send_presence_change_event(
entity: BaseThermostat, new_state: bool, old_state: bool, date, sleep=True
vtherm: BaseThermostat, new_state: bool, old_state: bool, date, sleep=True
):
"""Sending a new presence event simulating a change on the window state"""
_LOGGER.info(
@@ -845,26 +854,26 @@ async def send_presence_change_event(
new_state,
old_state,
date,
entity,
vtherm,
)
presence_event = Event(
EVENT_STATE_CHANGED,
{
"new_state": State(
entity_id=entity.entity_id,
entity_id=vtherm.entity_id,
state=STATE_ON if new_state else STATE_OFF,
last_changed=date,
last_updated=date,
),
"old_state": State(
entity_id=entity.entity_id,
entity_id=vtherm.entity_id,
state=STATE_ON if old_state else STATE_OFF,
last_changed=date,
last_updated=date,
),
},
)
ret = await entity._async_presence_changed(presence_event)
ret = await vtherm._presence_manager._presence_sensor_changed(presence_event)
if sleep:
await asyncio.sleep(0.1)
return ret

175
tests/test_presence.py Normal file
View File

@@ -0,0 +1,175 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long
""" Test the Security featrure """
import logging
from unittest.mock import patch, call, AsyncMock, MagicMock, PropertyMock
# from datetime import timedelta, datetime
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.presence_manager import (
FeaturePresenceManager,
)
from .commons import *
logging.getLogger().setLevel(logging.DEBUG)
@pytest.mark.parametrize(
"temp, absence, state, nb_call, presence_state, changed",
[
(19, False, STATE_ON, 1, STATE_ON, True),
(17, True, STATE_OFF, 1, STATE_OFF, True),
(19, False, STATE_HOME, 1, STATE_ON, True),
(17, True, STATE_NOT_HOME, 1, STATE_OFF, True),
(17, False, STATE_UNAVAILABLE, 0, STATE_UNKNOWN, False),
(17, False, STATE_UNKNOWN, 0, STATE_UNKNOWN, False),
(17, False, "wrong state", 0, STATE_UNKNOWN, False),
],
)
async def test_presence_feature_manager(
hass: HomeAssistant, temp, absence, state, nb_call, presence_state, changed
):
"""Test the FeaturePresenceManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
# 1. creation
presence_manager = FeaturePresenceManager(fake_vtherm, hass)
assert presence_manager is not None
assert presence_manager.is_configured is False
assert presence_manager.is_absence_detected is False
assert presence_manager.presence_state == STATE_UNAVAILABLE
assert presence_manager.name == "the name"
assert len(presence_manager._active_listener) == 0
custom_attributes = {}
presence_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["presence_sensor_entity_id"] is None
assert custom_attributes["presence_state"] == STATE_UNAVAILABLE
assert custom_attributes["presence_configured"] is False
# 2. post_init
presence_manager.post_init(
{
CONF_PRESENCE_SENSOR: "sensor.the_presence_sensor",
CONF_USE_PRESENCE_FEATURE: True,
}
)
assert presence_manager.is_configured is True
assert presence_manager.presence_state == STATE_UNKNOWN
assert presence_manager.is_absence_detected is False
custom_attributes = {}
presence_manager.add_custom_attributes(custom_attributes)
assert (
custom_attributes["presence_sensor_entity_id"] == "sensor.the_presence_sensor"
)
assert custom_attributes["presence_state"] == STATE_UNKNOWN
assert custom_attributes["presence_configured"] is True
# 3. start listening
presence_manager.start_listening()
assert presence_manager.is_configured is True
assert presence_manager.presence_state == STATE_UNKNOWN
assert presence_manager.is_absence_detected is False
assert len(presence_manager._active_listener) == 1
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_presence_sensor", state)) as mock_get_state:
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.find_preset_temp.return_value = temp
fake_vtherm.change_target_temperature = AsyncMock()
fake_vtherm.async_control_heating = AsyncMock()
ret = await presence_manager.refresh_state()
assert ret == changed
assert presence_manager.is_configured is True
assert presence_manager.presence_state == presence_state
assert presence_manager.is_absence_detected is absence
assert mock_get_state.call_count == 1
assert fake_vtherm.find_preset_temp.call_count == nb_call
if nb_call == 1:
fake_vtherm.find_preset_temp.assert_has_calls(
[
call.find_preset_temp(PRESET_COMFORT),
]
)
assert fake_vtherm.change_target_temperature.call_count == nb_call
fake_vtherm.change_target_temperature.assert_has_calls(
[
call.find_preset_temp(temp),
]
)
assert fake_vtherm.async_control_heating.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
presence_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["presence_sensor_entity_id"] == "sensor.the_presence_sensor"
assert custom_attributes["presence_state"] == presence_state
assert custom_attributes["presence_configured"] is True
# 6. test _presence_sensor_changed with the parametrized
fake_vtherm.find_preset_temp.return_value = temp
fake_vtherm.change_target_temperature = AsyncMock()
fake_vtherm.async_control_heating = AsyncMock()
await presence_manager._presence_sensor_changed(
event=Event(
event_type=EVENT_STATE_CHANGED,
data={
"entity_id": "sensor.the_presence_sensor",
"new_state": State("sensor.the_presence_sensor", state),
"old_state": State("sensor.the_presence_sensor", STATE_UNAVAILABLE),
}))
assert ret == changed
assert presence_manager.is_configured is True
assert presence_manager.presence_state == presence_state
assert presence_manager.is_absence_detected is absence
assert fake_vtherm.find_preset_temp.call_count == nb_call
if nb_call == 1:
fake_vtherm.find_preset_temp.assert_has_calls(
[
call.find_preset_temp(PRESET_COMFORT),
]
)
assert fake_vtherm.change_target_temperature.call_count == nb_call
fake_vtherm.change_target_temperature.assert_has_calls(
[
call.find_preset_temp(temp),
]
)
assert fake_vtherm.async_control_heating.call_count == 1
fake_vtherm.async_control_heating.assert_has_calls([
call.async_control_heating(force=True)
])
fake_vtherm.reset_mock()
# 7. Check custom_attributes
custom_attributes = {}
presence_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["presence_sensor_entity_id"] == "sensor.the_presence_sensor"
assert custom_attributes["presence_state"] == presence_state
assert custom_attributes["presence_configured"] is True