Issue #82 - unavailable climate goes into security mode
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
homeassistant==2023.9.1
|
||||
homeassistant==2023.10.1
|
||||
ffmpeg
|
||||
@@ -83,7 +83,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class MockClimate(ClimateEntity):
|
||||
"""A Mock Climate class used for Underlying climate mode"""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos, hvac_mode:HVACMode = HVACMode.OFF) -> None:
|
||||
"""Initialize the thermostat."""
|
||||
|
||||
super().__init__()
|
||||
@@ -93,10 +93,26 @@ class MockClimate(ClimateEntity):
|
||||
self._unique_id = unique_id
|
||||
self._name = name
|
||||
self._attr_hvac_action = HVACAction.OFF
|
||||
self._attr_hvac_mode = HVACMode.OFF
|
||||
self._attr_hvac_mode = hvac_mode
|
||||
self._attr_hvac_modes = [HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT]
|
||||
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
|
||||
class MockUnavailableClimate(ClimateEntity):
|
||||
"""A Mock Climate class used for Underlying climate mode"""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
||||
"""Initialize the thermostat."""
|
||||
|
||||
super().__init__()
|
||||
|
||||
self._hass = hass
|
||||
self._attr_extra_state_attributes = {}
|
||||
self._unique_id = unique_id
|
||||
self._name = name
|
||||
self._attr_hvac_action = None
|
||||
self._attr_hvac_mode = None
|
||||
self._attr_hvac_modes = [HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT]
|
||||
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
|
||||
class MagicMockClimate(MagicMock):
|
||||
"""A Magic Mock class for a underlying climate entity"""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
""" Test the Window management """
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, call
|
||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@@ -343,3 +343,108 @@ async def test_bug_66(
|
||||
assert entity.window_state == STATE_OFF
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_bug_82(
|
||||
hass: HomeAssistant,
|
||||
skip_hass_states_is_state,
|
||||
skip_turn_on_off_heater,
|
||||
skip_send_event,
|
||||
):
|
||||
"""Test that when a underlying climate is not available the VTherm doesn't go into security mode"""
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now: datetime = datetime.now(tz=tz)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverClimateMockName",
|
||||
unique_id="uniqueId",
|
||||
data=PARTIAL_CLIMATE_CONFIG, # 5 minutes security delay
|
||||
)
|
||||
|
||||
fake_underlying_climate = MockUnavailableClimate(hass, "mockUniqueId", "MockClimateName", {})
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||
return_value=fake_underlying_climate,
|
||||
) as mock_find_climate:
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
def find_my_entity(entity_id) -> ClimateEntity:
|
||||
"""Find my new entity"""
|
||||
component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN]
|
||||
for entity in component.entities:
|
||||
if entity.entity_id == entity_id:
|
||||
return entity
|
||||
|
||||
entity = find_my_entity("climate.theoverclimatemockname")
|
||||
|
||||
assert entity
|
||||
|
||||
assert entity.name == "TheOverClimateMockName"
|
||||
assert entity._is_over_climate is True
|
||||
# assert entity.hvac_action is HVACAction.OFF
|
||||
# assert entity.hvac_mode is HVACMode.OFF
|
||||
assert entity.hvac_mode is None
|
||||
assert entity.target_temperature == entity.min_temp
|
||||
assert entity.preset_modes == [
|
||||
PRESET_NONE,
|
||||
PRESET_ECO,
|
||||
PRESET_COMFORT,
|
||||
PRESET_BOOST,
|
||||
]
|
||||
assert entity.preset_mode is PRESET_NONE
|
||||
assert entity._security_state is False
|
||||
|
||||
# should have been called with EventType.PRESET_EVENT and EventType.HVAC_MODE_EVENT
|
||||
assert mock_send_event.call_count == 2
|
||||
mock_send_event.assert_has_calls(
|
||||
[
|
||||
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_NONE}),
|
||||
call.send_event(
|
||||
EventType.HVAC_MODE_EVENT,
|
||||
{"hvac_mode": HVACMode.OFF},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
assert mock_find_climate.call_count == 1
|
||||
assert mock_find_climate.mock_calls[0] == call()
|
||||
mock_find_climate.assert_has_calls([call.find_underlying_entity()])
|
||||
|
||||
# Force security mode
|
||||
assert entity._last_ext_temperature_mesure is not None
|
||||
assert entity._last_temperature_mesure is not None
|
||||
assert (entity._last_temperature_mesure.astimezone(tz) - now).total_seconds() < 1
|
||||
assert (
|
||||
entity._last_ext_temperature_mesure.astimezone(tz) - now
|
||||
).total_seconds() < 1
|
||||
|
||||
# Tries to turns on the Thermostat
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
assert entity.hvac_mode == None
|
||||
|
||||
# 2. activate security feature when date is expired
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on:
|
||||
event_timestamp = now - timedelta(minutes=6)
|
||||
|
||||
# set temperature to 15 so that on_percent will be > security_min_on_percent (0.2)
|
||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||
# Should stay False
|
||||
assert entity.security_state is False
|
||||
assert entity.preset_mode == 'none'
|
||||
assert entity._saved_preset_mode == 'none'
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -189,3 +189,103 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
|
||||
# Heater is now on
|
||||
assert mock_heater_on.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_security_over_climate(
|
||||
hass: HomeAssistant,
|
||||
skip_hass_states_is_state,
|
||||
skip_turn_on_off_heater,
|
||||
skip_send_event,
|
||||
):
|
||||
"""Test that when a underlying climate is not available the VTherm doesn't go into security mode"""
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now: datetime = datetime.now(tz=tz)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverClimateMockName",
|
||||
unique_id="uniqueId",
|
||||
data=PARTIAL_CLIMATE_CONFIG, # 5 minutes security delay
|
||||
)
|
||||
|
||||
fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {}, HVACMode.HEAT)
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||
return_value=fake_underlying_climate,
|
||||
) as mock_find_climate:
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
def find_my_entity(entity_id) -> ClimateEntity:
|
||||
"""Find my new entity"""
|
||||
component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN]
|
||||
for entity in component.entities:
|
||||
if entity.entity_id == entity_id:
|
||||
return entity
|
||||
|
||||
entity = find_my_entity("climate.theoverclimatemockname")
|
||||
|
||||
assert entity
|
||||
|
||||
assert entity.name == "TheOverClimateMockName"
|
||||
assert entity._is_over_climate is True
|
||||
assert entity.hvac_action is HVACAction.OFF
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.target_temperature == entity.min_temp
|
||||
assert entity.preset_modes == [
|
||||
PRESET_NONE,
|
||||
PRESET_ECO,
|
||||
PRESET_COMFORT,
|
||||
PRESET_BOOST,
|
||||
]
|
||||
assert entity.preset_mode is PRESET_NONE
|
||||
assert entity._security_state is False
|
||||
|
||||
# should have been called with EventType.PRESET_EVENT and EventType.HVAC_MODE_EVENT
|
||||
assert mock_send_event.call_count == 2
|
||||
mock_send_event.assert_has_calls(
|
||||
[
|
||||
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_NONE}),
|
||||
call.send_event(
|
||||
EventType.HVAC_MODE_EVENT,
|
||||
{"hvac_mode": HVACMode.OFF},
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
assert mock_find_climate.call_count == 1
|
||||
assert mock_find_climate.mock_calls[0] == call()
|
||||
mock_find_climate.assert_has_calls([call.find_underlying_entity()])
|
||||
|
||||
# Force security mode
|
||||
assert entity._last_ext_temperature_mesure is not None
|
||||
assert entity._last_temperature_mesure is not None
|
||||
assert (entity._last_temperature_mesure.astimezone(tz) - now).total_seconds() < 1
|
||||
assert (
|
||||
entity._last_ext_temperature_mesure.astimezone(tz) - now
|
||||
).total_seconds() < 1
|
||||
|
||||
# Tries to turns on the Thermostat
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
assert entity.hvac_mode == HVACMode.HEAT
|
||||
|
||||
# 2. activate security feature when date is expired
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.climate.VersatileThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on:
|
||||
event_timestamp = now - timedelta(minutes=6)
|
||||
|
||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||
# Should stay False because a climate is never in security mode
|
||||
assert entity.security_state is False
|
||||
assert entity.preset_mode == 'none'
|
||||
assert entity._saved_preset_mode == 'none'
|
||||
Reference in New Issue
Block a user