Fix legacy _attr_state handling in AlarmControlPanel (#130479)

This commit is contained in:
G Johansson 2024-11-13 10:55:28 +01:00 committed by GitHub
parent 3092297979
commit 0ac00ef092
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 103 additions and 4 deletions

View file

@ -6,7 +6,7 @@ import asyncio
from datetime import timedelta from datetime import timedelta
from functools import partial from functools import partial
import logging import logging
from typing import Any, Final, final from typing import TYPE_CHECKING, Any, Final, final
from propcache import cached_property from propcache import cached_property
import voluptuous as vol import voluptuous as vol
@ -221,9 +221,15 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
@property @property
def state(self) -> str | None: def state(self) -> str | None:
"""Return the current state.""" """Return the current state."""
if (alarm_state := self.alarm_state) is None: if (alarm_state := self.alarm_state) is not None:
return None return alarm_state
return alarm_state if self._attr_state is not None:
# Backwards compatibility for integrations that set state directly
# Should be removed in 2025.11
if TYPE_CHECKING:
assert isinstance(self._attr_state, str)
return self._attr_state
return None
@cached_property @cached_property
def alarm_state(self) -> AlarmControlPanelState | None: def alarm_state(self) -> AlarmControlPanelState | None:

View file

@ -489,3 +489,96 @@ async def test_alarm_control_panel_log_deprecated_state_warning_using_attr_state
) )
# Test we only log once # Test we only log once
assert "Entities should implement the 'alarm_state' property and" not in caplog.text assert "Entities should implement the 'alarm_state' property and" not in caplog.text
async def test_alarm_control_panel_deprecated_state_does_not_break_state(
hass: HomeAssistant,
code_format: CodeFormat | None,
supported_features: AlarmControlPanelEntityFeature,
code_arm_required: bool,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test using _attr_state attribute does not break state."""
async def async_setup_entry_init(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up test config entry."""
await hass.config_entries.async_forward_entry_setups(
config_entry, [ALARM_CONTROL_PANEL_DOMAIN]
)
return True
mock_integration(
hass,
MockModule(
TEST_DOMAIN,
async_setup_entry=async_setup_entry_init,
),
)
class MockLegacyAlarmControlPanel(MockAlarmControlPanel):
"""Mocked alarm control entity."""
def __init__(
self,
supported_features: AlarmControlPanelEntityFeature = AlarmControlPanelEntityFeature(
0
),
code_format: CodeFormat | None = None,
code_arm_required: bool = True,
) -> None:
"""Initialize the alarm control."""
self._attr_state = "armed_away"
super().__init__(supported_features, code_format, code_arm_required)
def alarm_disarm(self, code: str | None = None) -> None:
"""Mock alarm disarm calls."""
self._attr_state = "disarmed"
entity = MockLegacyAlarmControlPanel(
supported_features=supported_features,
code_format=code_format,
code_arm_required=code_arm_required,
)
async def async_setup_entry_platform(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up test alarm control panel platform via config entry."""
async_add_entities([entity])
mock_platform(
hass,
f"{TEST_DOMAIN}.{ALARM_CONTROL_PANEL_DOMAIN}",
MockPlatform(async_setup_entry=async_setup_entry_platform),
)
with patch.object(
MockLegacyAlarmControlPanel,
"__module__",
"tests.custom_components.test.alarm_control_panel",
):
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(entity.entity_id)
assert state is not None
assert state.state == "armed_away"
with patch.object(
MockLegacyAlarmControlPanel,
"__module__",
"tests.custom_components.test.alarm_control_panel",
):
await help_test_async_alarm_control_panel_service(
hass, entity.entity_id, SERVICE_ALARM_DISARM
)
state = hass.states.get(entity.entity_id)
assert state is not None
assert state.state == "disarmed"