Skip to content

Commit f05fef9

Browse files
Add bad code attempt event to manual alarm control panel (home-assistant#146315)
Co-authored-by: Erik Montnemery <[email protected]>
1 parent a257b5c commit f05fef9

File tree

2 files changed

+91
-1
lines changed

2 files changed

+91
-1
lines changed

homeassistant/components/manual/alarm_control_panel.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,20 @@ def _async_validate_code(self, code: str | None, state: str) -> None:
408408
if not alarm_code or code == alarm_code:
409409
return
410410

411+
current_context = (
412+
self._context if hasattr(self, "_context") and self._context else None
413+
)
414+
user_id_from_context = current_context.user_id if current_context else None
415+
416+
self.hass.bus.async_fire(
417+
"manual_alarm_bad_code_attempt",
418+
{
419+
"entity_id": self.entity_id,
420+
"user_id": user_id_from_context,
421+
"target_state": state,
422+
},
423+
)
424+
411425
raise ServiceValidationError(
412426
"Invalid alarm code provided",
413427
translation_domain=DOMAIN,

tests/components/manual/test_alarm_control_panel.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
from freezegun import freeze_time
77
import pytest
88

9+
from homeassistant.auth.models import User
910
from homeassistant.components import alarm_control_panel
1011
from homeassistant.components.alarm_control_panel import (
12+
DOMAIN as ALARM_DOMAIN,
1113
AlarmControlPanelEntityFeature,
1214
AlarmControlPanelState,
1315
)
@@ -25,7 +27,7 @@
2527
SERVICE_ALARM_ARM_NIGHT,
2628
SERVICE_ALARM_ARM_VACATION,
2729
)
28-
from homeassistant.core import CoreState, HomeAssistant, State
30+
from homeassistant.core import Context, CoreState, HomeAssistant, State, callback
2931
from homeassistant.exceptions import ServiceValidationError
3032
from homeassistant.setup import async_setup_component
3133
from homeassistant.util import dt as dt_util
@@ -1123,6 +1125,80 @@ async def test_disarm_during_trigger_with_invalid_code(hass: HomeAssistant) -> N
11231125
assert state.state == AlarmControlPanelState.TRIGGERED
11241126

11251127

1128+
async def test_bad_code_attempt_event_fired(hass: HomeAssistant) -> None:
1129+
"""Test that manual_alarm_bad_code_attempt event is fired on bad code."""
1130+
1131+
entity_id = "alarm_control_panel.test_alarm"
1132+
config = {
1133+
ALARM_DOMAIN: {
1134+
"platform": "manual",
1135+
"name": "Test Alarm",
1136+
"code": "1234",
1137+
"delay_time": 0,
1138+
"arming_time": 0,
1139+
"trigger_time": 0,
1140+
}
1141+
}
1142+
assert await async_setup_component(hass, ALARM_DOMAIN, config)
1143+
await hass.async_block_till_done()
1144+
1145+
alarm_entity = hass.states.get(entity_id)
1146+
assert alarm_entity is not None
1147+
1148+
await hass.services.async_call(
1149+
ALARM_DOMAIN,
1150+
"alarm_arm_away",
1151+
{"entity_id": entity_id, "code": "1234"},
1152+
blocking=True,
1153+
)
1154+
await hass.async_block_till_done()
1155+
assert hass.states.get(entity_id).state == AlarmControlPanelState.ARMED_AWAY
1156+
1157+
bad_code = "0000"
1158+
1159+
mock_user_id = "test_user_id_123"
1160+
test_context = Context(user_id=mock_user_id)
1161+
1162+
events = []
1163+
1164+
@callback
1165+
def event_listener(event):
1166+
events.append(event.data)
1167+
1168+
hass.bus.async_listen("manual_alarm_bad_code_attempt", event_listener)
1169+
1170+
await hass.services.async_call(
1171+
ALARM_DOMAIN,
1172+
"alarm_disarm",
1173+
{"entity_id": entity_id, "code": "1234"},
1174+
blocking=True,
1175+
)
1176+
await hass.async_block_till_done()
1177+
1178+
assert len(events) == 0
1179+
1180+
with patch("homeassistant.auth.AuthManager.async_get_user") as mock_get_user:
1181+
mock_user = MagicMock(spec=User)
1182+
mock_user.id = mock_user_id
1183+
mock_get_user.return_value = mock_user
1184+
1185+
with pytest.raises(ServiceValidationError):
1186+
await hass.services.async_call(
1187+
ALARM_DOMAIN,
1188+
"alarm_disarm",
1189+
{"entity_id": entity_id, "code": bad_code},
1190+
blocking=True,
1191+
context=test_context,
1192+
)
1193+
1194+
await hass.async_block_till_done()
1195+
1196+
assert len(events) == 1
1197+
assert events[0].get("entity_id") == entity_id
1198+
assert events[0].get("target_state") == AlarmControlPanelState.DISARMED
1199+
assert events[0].get("user_id") == mock_user_id
1200+
1201+
11261202
async def test_disarm_with_template_code(hass: HomeAssistant) -> None:
11271203
"""Attempt to disarm with a valid or invalid template-based code."""
11281204
assert await async_setup_component(

0 commit comments

Comments
 (0)