Add bad code attempt event to manual alarm control panel (#146315)

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
giuseppeg88
2025-11-05 19:15:27 +01:00
committed by GitHub
parent a257b5c54c
commit f05fef9588
2 changed files with 91 additions and 1 deletions

View File

@@ -408,6 +408,20 @@ class ManualAlarm(AlarmControlPanelEntity, RestoreEntity):
if not alarm_code or code == alarm_code:
return
current_context = (
self._context if hasattr(self, "_context") and self._context else None
)
user_id_from_context = current_context.user_id if current_context else None
self.hass.bus.async_fire(
"manual_alarm_bad_code_attempt",
{
"entity_id": self.entity_id,
"user_id": user_id_from_context,
"target_state": state,
},
)
raise ServiceValidationError(
"Invalid alarm code provided",
translation_domain=DOMAIN,

View File

@@ -6,8 +6,10 @@ from unittest.mock import MagicMock, patch
from freezegun import freeze_time
import pytest
from homeassistant.auth.models import User
from homeassistant.components import alarm_control_panel
from homeassistant.components.alarm_control_panel import (
DOMAIN as ALARM_DOMAIN,
AlarmControlPanelEntityFeature,
AlarmControlPanelState,
)
@@ -25,7 +27,7 @@ from homeassistant.const import (
SERVICE_ALARM_ARM_NIGHT,
SERVICE_ALARM_ARM_VACATION,
)
from homeassistant.core import CoreState, HomeAssistant, State
from homeassistant.core import Context, CoreState, HomeAssistant, State, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
@@ -1123,6 +1125,80 @@ async def test_disarm_during_trigger_with_invalid_code(hass: HomeAssistant) -> N
assert state.state == AlarmControlPanelState.TRIGGERED
async def test_bad_code_attempt_event_fired(hass: HomeAssistant) -> None:
"""Test that manual_alarm_bad_code_attempt event is fired on bad code."""
entity_id = "alarm_control_panel.test_alarm"
config = {
ALARM_DOMAIN: {
"platform": "manual",
"name": "Test Alarm",
"code": "1234",
"delay_time": 0,
"arming_time": 0,
"trigger_time": 0,
}
}
assert await async_setup_component(hass, ALARM_DOMAIN, config)
await hass.async_block_till_done()
alarm_entity = hass.states.get(entity_id)
assert alarm_entity is not None
await hass.services.async_call(
ALARM_DOMAIN,
"alarm_arm_away",
{"entity_id": entity_id, "code": "1234"},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == AlarmControlPanelState.ARMED_AWAY
bad_code = "0000"
mock_user_id = "test_user_id_123"
test_context = Context(user_id=mock_user_id)
events = []
@callback
def event_listener(event):
events.append(event.data)
hass.bus.async_listen("manual_alarm_bad_code_attempt", event_listener)
await hass.services.async_call(
ALARM_DOMAIN,
"alarm_disarm",
{"entity_id": entity_id, "code": "1234"},
blocking=True,
)
await hass.async_block_till_done()
assert len(events) == 0
with patch("homeassistant.auth.AuthManager.async_get_user") as mock_get_user:
mock_user = MagicMock(spec=User)
mock_user.id = mock_user_id
mock_get_user.return_value = mock_user
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
ALARM_DOMAIN,
"alarm_disarm",
{"entity_id": entity_id, "code": bad_code},
blocking=True,
context=test_context,
)
await hass.async_block_till_done()
assert len(events) == 1
assert events[0].get("entity_id") == entity_id
assert events[0].get("target_state") == AlarmControlPanelState.DISARMED
assert events[0].get("user_id") == mock_user_id
async def test_disarm_with_template_code(hass: HomeAssistant) -> None:
"""Attempt to disarm with a valid or invalid template-based code."""
assert await async_setup_component(