Skip to content

Commit ac46568

Browse files
Add tests to concord232 component (home-assistant#156070)
Co-authored-by: Joost Lekkerkerker <[email protected]>
1 parent 7c1b8ee commit ac46568

File tree

5 files changed

+518
-0
lines changed

5 files changed

+518
-0
lines changed

requirements_test_all.txt

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Tests for the Concord232 integration."""
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Fixtures for the Concord232 integration."""
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Generator
6+
from unittest.mock import MagicMock, patch
7+
8+
import pytest
9+
10+
11+
@pytest.fixture
12+
def mock_concord232_client() -> Generator[MagicMock]:
13+
"""Mock the concord232 Client for easier testing."""
14+
with (
15+
patch(
16+
"homeassistant.components.concord232.alarm_control_panel.concord232_client.Client",
17+
autospec=True,
18+
) as mock_client_class,
19+
patch(
20+
"homeassistant.components.concord232.binary_sensor.concord232_client.Client",
21+
new=mock_client_class,
22+
),
23+
):
24+
mock_instance = mock_client_class.return_value
25+
26+
# Set up default return values
27+
mock_instance.list_partitions.return_value = [{"arming_level": "Off"}]
28+
mock_instance.list_zones.return_value = [
29+
{"number": 1, "name": "Zone 1", "state": "Normal"},
30+
{"number": 2, "name": "Zone 2", "state": "Normal"},
31+
]
32+
33+
yield mock_instance
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
"""Tests for the Concord232 alarm control panel platform."""
2+
3+
from __future__ import annotations
4+
5+
from unittest.mock import MagicMock
6+
7+
from freezegun.api import FrozenDateTimeFactory
8+
import pytest
9+
import requests
10+
11+
from homeassistant.components.alarm_control_panel import (
12+
DOMAIN as ALARM_DOMAIN,
13+
SERVICE_ALARM_ARM_AWAY,
14+
SERVICE_ALARM_ARM_HOME,
15+
SERVICE_ALARM_DISARM,
16+
AlarmControlPanelState,
17+
)
18+
from homeassistant.const import (
19+
ATTR_CODE,
20+
ATTR_ENTITY_ID,
21+
CONF_CODE,
22+
CONF_HOST,
23+
CONF_MODE,
24+
CONF_NAME,
25+
CONF_PORT,
26+
)
27+
from homeassistant.core import HomeAssistant
28+
from homeassistant.setup import async_setup_component
29+
30+
from tests.common import async_fire_time_changed
31+
32+
VALID_CONFIG = {
33+
ALARM_DOMAIN: {
34+
"platform": "concord232",
35+
CONF_HOST: "localhost",
36+
CONF_PORT: 5007,
37+
CONF_NAME: "Test Alarm",
38+
}
39+
}
40+
41+
VALID_CONFIG_WITH_CODE = {
42+
ALARM_DOMAIN: {
43+
"platform": "concord232",
44+
CONF_HOST: "localhost",
45+
CONF_PORT: 5007,
46+
CONF_NAME: "Test Alarm",
47+
CONF_CODE: "1234",
48+
}
49+
}
50+
51+
VALID_CONFIG_SILENT_MODE = {
52+
ALARM_DOMAIN: {
53+
"platform": "concord232",
54+
CONF_HOST: "localhost",
55+
CONF_PORT: 5007,
56+
CONF_NAME: "Test Alarm",
57+
CONF_MODE: "silent",
58+
}
59+
}
60+
61+
62+
async def test_setup_platform(
63+
hass: HomeAssistant, mock_concord232_client: MagicMock
64+
) -> None:
65+
"""Test platform setup."""
66+
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
67+
await hass.async_block_till_done()
68+
69+
state = hass.states.get("alarm_control_panel.test_alarm")
70+
assert state is not None
71+
assert state.state == AlarmControlPanelState.DISARMED
72+
73+
74+
async def test_setup_platform_connection_error(
75+
hass: HomeAssistant, mock_concord232_client: MagicMock
76+
) -> None:
77+
"""Test platform setup with connection error."""
78+
mock_concord232_client.list_partitions.side_effect = (
79+
requests.exceptions.ConnectionError("Connection failed")
80+
)
81+
82+
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
83+
await hass.async_block_till_done()
84+
85+
assert hass.states.get("alarm_control_panel.test_alarm") is None
86+
87+
88+
async def test_alarm_disarm(
89+
hass: HomeAssistant, mock_concord232_client: MagicMock
90+
) -> None:
91+
"""Test disarm service."""
92+
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
93+
await hass.async_block_till_done()
94+
95+
await hass.services.async_call(
96+
ALARM_DOMAIN,
97+
SERVICE_ALARM_DISARM,
98+
{ATTR_ENTITY_ID: "alarm_control_panel.test_alarm"},
99+
blocking=True,
100+
)
101+
mock_concord232_client.disarm.assert_called_once_with(None)
102+
103+
104+
async def test_alarm_disarm_with_code(
105+
hass: HomeAssistant, mock_concord232_client: MagicMock
106+
) -> None:
107+
"""Test disarm service with code."""
108+
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG_WITH_CODE)
109+
await hass.async_block_till_done()
110+
111+
await hass.services.async_call(
112+
ALARM_DOMAIN,
113+
SERVICE_ALARM_DISARM,
114+
{
115+
ATTR_ENTITY_ID: "alarm_control_panel.test_alarm",
116+
ATTR_CODE: "1234",
117+
},
118+
blocking=True,
119+
)
120+
mock_concord232_client.disarm.assert_called_once_with("1234")
121+
122+
123+
async def test_alarm_disarm_invalid_code(
124+
hass: HomeAssistant,
125+
mock_concord232_client: MagicMock,
126+
caplog: pytest.LogCaptureFixture,
127+
) -> None:
128+
"""Test disarm service with invalid code."""
129+
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG_WITH_CODE)
130+
await hass.async_block_till_done()
131+
132+
await hass.services.async_call(
133+
ALARM_DOMAIN,
134+
SERVICE_ALARM_DISARM,
135+
{
136+
ATTR_ENTITY_ID: "alarm_control_panel.test_alarm",
137+
ATTR_CODE: "9999",
138+
},
139+
blocking=True,
140+
)
141+
mock_concord232_client.disarm.assert_not_called()
142+
assert "Invalid code given" in caplog.text
143+
144+
145+
@pytest.mark.parametrize(
146+
("service", "expected_arm_call"),
147+
[
148+
(SERVICE_ALARM_ARM_HOME, "stay"),
149+
(SERVICE_ALARM_ARM_AWAY, "away"),
150+
],
151+
)
152+
async def test_alarm_arm(
153+
hass: HomeAssistant,
154+
mock_concord232_client: MagicMock,
155+
service: str,
156+
expected_arm_call: str,
157+
) -> None:
158+
"""Test arm service."""
159+
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG_WITH_CODE)
160+
await hass.async_block_till_done()
161+
162+
await hass.services.async_call(
163+
ALARM_DOMAIN,
164+
service,
165+
{
166+
ATTR_ENTITY_ID: "alarm_control_panel.test_alarm",
167+
ATTR_CODE: "1234",
168+
},
169+
blocking=True,
170+
)
171+
mock_concord232_client.arm.assert_called_once_with(expected_arm_call)
172+
173+
174+
async def test_alarm_arm_home_silent_mode(
175+
hass: HomeAssistant, mock_concord232_client: MagicMock
176+
) -> None:
177+
"""Test arm home service with silent mode."""
178+
config_with_code = VALID_CONFIG_SILENT_MODE.copy()
179+
config_with_code[ALARM_DOMAIN][CONF_CODE] = "1234"
180+
await async_setup_component(hass, ALARM_DOMAIN, config_with_code)
181+
await hass.async_block_till_done()
182+
183+
await hass.services.async_call(
184+
ALARM_DOMAIN,
185+
SERVICE_ALARM_ARM_HOME,
186+
{
187+
ATTR_ENTITY_ID: "alarm_control_panel.test_alarm",
188+
ATTR_CODE: "1234",
189+
},
190+
blocking=True,
191+
)
192+
mock_concord232_client.arm.assert_called_once_with("stay", "silent")
193+
194+
195+
async def test_update_state_disarmed(
196+
hass: HomeAssistant, mock_concord232_client: MagicMock
197+
) -> None:
198+
"""Test update when alarm is disarmed."""
199+
mock_concord232_client.list_partitions.return_value = [{"arming_level": "Off"}]
200+
mock_concord232_client.list_zones.return_value = [
201+
{"number": 1, "name": "Zone 1", "state": "Normal"},
202+
]
203+
204+
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
205+
await hass.async_block_till_done()
206+
207+
state = hass.states.get("alarm_control_panel.test_alarm")
208+
assert state.state == AlarmControlPanelState.DISARMED
209+
210+
211+
@pytest.mark.parametrize(
212+
("arming_level", "expected_state"),
213+
[
214+
("Home", AlarmControlPanelState.ARMED_HOME),
215+
("Away", AlarmControlPanelState.ARMED_AWAY),
216+
],
217+
)
218+
async def test_update_state_armed(
219+
hass: HomeAssistant,
220+
mock_concord232_client: MagicMock,
221+
freezer: FrozenDateTimeFactory,
222+
arming_level: str,
223+
expected_state: str,
224+
) -> None:
225+
"""Test update when alarm is armed."""
226+
mock_concord232_client.list_partitions.return_value = [
227+
{"arming_level": arming_level}
228+
]
229+
mock_concord232_client.partitions = (
230+
mock_concord232_client.list_partitions.return_value
231+
)
232+
mock_concord232_client.list_zones.return_value = [
233+
{"number": 1, "name": "Zone 1", "state": "Normal"},
234+
]
235+
236+
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
237+
await hass.async_block_till_done()
238+
239+
# Trigger update
240+
freezer.tick(10)
241+
async_fire_time_changed(hass)
242+
await hass.async_block_till_done()
243+
244+
state = hass.states.get("alarm_control_panel.test_alarm")
245+
assert state.state == expected_state
246+
247+
248+
async def test_update_connection_error(
249+
hass: HomeAssistant,
250+
mock_concord232_client: MagicMock,
251+
freezer: FrozenDateTimeFactory,
252+
caplog: pytest.LogCaptureFixture,
253+
) -> None:
254+
"""Test update with connection error."""
255+
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
256+
await hass.async_block_till_done()
257+
258+
mock_concord232_client.list_partitions.side_effect = (
259+
requests.exceptions.ConnectionError("Connection failed")
260+
)
261+
262+
freezer.tick(10)
263+
async_fire_time_changed(hass)
264+
await hass.async_block_till_done()
265+
266+
assert "Unable to connect to" in caplog.text
267+
268+
269+
async def test_update_no_partitions(
270+
hass: HomeAssistant,
271+
mock_concord232_client: MagicMock,
272+
caplog: pytest.LogCaptureFixture,
273+
) -> None:
274+
"""Test update when no partitions are available."""
275+
mock_concord232_client.list_partitions.return_value = []
276+
277+
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
278+
await hass.async_block_till_done()
279+
280+
assert "Concord232 reports no partitions" in caplog.text

0 commit comments

Comments
 (0)