mirror of
https://github.com/home-assistant/core.git
synced 2026-04-29 02:13:44 +02:00
Add tests to concord232 component (#156070)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Generated
+3
@@ -649,6 +649,9 @@ colorthief==0.2.1
|
||||
# homeassistant.components.compit
|
||||
compit-inext-api==0.3.1
|
||||
|
||||
# homeassistant.components.concord232
|
||||
concord232==0.15.1
|
||||
|
||||
# homeassistant.components.xiaomi_miio
|
||||
construct==2.10.68
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Tests for the Concord232 integration."""
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Fixtures for the Concord232 integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_concord232_client() -> Generator[MagicMock]:
|
||||
"""Mock the concord232 Client for easier testing."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.concord232.alarm_control_panel.concord232_client.Client",
|
||||
autospec=True,
|
||||
) as mock_client_class,
|
||||
patch(
|
||||
"homeassistant.components.concord232.binary_sensor.concord232_client.Client",
|
||||
new=mock_client_class,
|
||||
),
|
||||
):
|
||||
mock_instance = mock_client_class.return_value
|
||||
|
||||
# Set up default return values
|
||||
mock_instance.list_partitions.return_value = [{"arming_level": "Off"}]
|
||||
mock_instance.list_zones.return_value = [
|
||||
{"number": 1, "name": "Zone 1", "state": "Normal"},
|
||||
{"number": 2, "name": "Zone 2", "state": "Normal"},
|
||||
]
|
||||
|
||||
yield mock_instance
|
||||
@@ -0,0 +1,280 @@
|
||||
"""Tests for the Concord232 alarm control panel platform."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
DOMAIN as ALARM_DOMAIN,
|
||||
SERVICE_ALARM_ARM_AWAY,
|
||||
SERVICE_ALARM_ARM_HOME,
|
||||
SERVICE_ALARM_DISARM,
|
||||
AlarmControlPanelState,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE,
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_CODE,
|
||||
CONF_HOST,
|
||||
CONF_MODE,
|
||||
CONF_NAME,
|
||||
CONF_PORT,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
VALID_CONFIG = {
|
||||
ALARM_DOMAIN: {
|
||||
"platform": "concord232",
|
||||
CONF_HOST: "localhost",
|
||||
CONF_PORT: 5007,
|
||||
CONF_NAME: "Test Alarm",
|
||||
}
|
||||
}
|
||||
|
||||
VALID_CONFIG_WITH_CODE = {
|
||||
ALARM_DOMAIN: {
|
||||
"platform": "concord232",
|
||||
CONF_HOST: "localhost",
|
||||
CONF_PORT: 5007,
|
||||
CONF_NAME: "Test Alarm",
|
||||
CONF_CODE: "1234",
|
||||
}
|
||||
}
|
||||
|
||||
VALID_CONFIG_SILENT_MODE = {
|
||||
ALARM_DOMAIN: {
|
||||
"platform": "concord232",
|
||||
CONF_HOST: "localhost",
|
||||
CONF_PORT: 5007,
|
||||
CONF_NAME: "Test Alarm",
|
||||
CONF_MODE: "silent",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_setup_platform(
|
||||
hass: HomeAssistant, mock_concord232_client: MagicMock
|
||||
) -> None:
|
||||
"""Test platform setup."""
|
||||
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("alarm_control_panel.test_alarm")
|
||||
assert state is not None
|
||||
assert state.state == AlarmControlPanelState.DISARMED
|
||||
|
||||
|
||||
async def test_setup_platform_connection_error(
|
||||
hass: HomeAssistant, mock_concord232_client: MagicMock
|
||||
) -> None:
|
||||
"""Test platform setup with connection error."""
|
||||
mock_concord232_client.list_partitions.side_effect = (
|
||||
requests.exceptions.ConnectionError("Connection failed")
|
||||
)
|
||||
|
||||
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("alarm_control_panel.test_alarm") is None
|
||||
|
||||
|
||||
async def test_alarm_disarm(
|
||||
hass: HomeAssistant, mock_concord232_client: MagicMock
|
||||
) -> None:
|
||||
"""Test disarm service."""
|
||||
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
ALARM_DOMAIN,
|
||||
SERVICE_ALARM_DISARM,
|
||||
{ATTR_ENTITY_ID: "alarm_control_panel.test_alarm"},
|
||||
blocking=True,
|
||||
)
|
||||
mock_concord232_client.disarm.assert_called_once_with(None)
|
||||
|
||||
|
||||
async def test_alarm_disarm_with_code(
|
||||
hass: HomeAssistant, mock_concord232_client: MagicMock
|
||||
) -> None:
|
||||
"""Test disarm service with code."""
|
||||
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG_WITH_CODE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
ALARM_DOMAIN,
|
||||
SERVICE_ALARM_DISARM,
|
||||
{
|
||||
ATTR_ENTITY_ID: "alarm_control_panel.test_alarm",
|
||||
ATTR_CODE: "1234",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_concord232_client.disarm.assert_called_once_with("1234")
|
||||
|
||||
|
||||
async def test_alarm_disarm_invalid_code(
|
||||
hass: HomeAssistant,
|
||||
mock_concord232_client: MagicMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test disarm service with invalid code."""
|
||||
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG_WITH_CODE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
ALARM_DOMAIN,
|
||||
SERVICE_ALARM_DISARM,
|
||||
{
|
||||
ATTR_ENTITY_ID: "alarm_control_panel.test_alarm",
|
||||
ATTR_CODE: "9999",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_concord232_client.disarm.assert_not_called()
|
||||
assert "Invalid code given" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service", "expected_arm_call"),
|
||||
[
|
||||
(SERVICE_ALARM_ARM_HOME, "stay"),
|
||||
(SERVICE_ALARM_ARM_AWAY, "away"),
|
||||
],
|
||||
)
|
||||
async def test_alarm_arm(
|
||||
hass: HomeAssistant,
|
||||
mock_concord232_client: MagicMock,
|
||||
service: str,
|
||||
expected_arm_call: str,
|
||||
) -> None:
|
||||
"""Test arm service."""
|
||||
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG_WITH_CODE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
ALARM_DOMAIN,
|
||||
service,
|
||||
{
|
||||
ATTR_ENTITY_ID: "alarm_control_panel.test_alarm",
|
||||
ATTR_CODE: "1234",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_concord232_client.arm.assert_called_once_with(expected_arm_call)
|
||||
|
||||
|
||||
async def test_alarm_arm_home_silent_mode(
|
||||
hass: HomeAssistant, mock_concord232_client: MagicMock
|
||||
) -> None:
|
||||
"""Test arm home service with silent mode."""
|
||||
config_with_code = VALID_CONFIG_SILENT_MODE.copy()
|
||||
config_with_code[ALARM_DOMAIN][CONF_CODE] = "1234"
|
||||
await async_setup_component(hass, ALARM_DOMAIN, config_with_code)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.services.async_call(
|
||||
ALARM_DOMAIN,
|
||||
SERVICE_ALARM_ARM_HOME,
|
||||
{
|
||||
ATTR_ENTITY_ID: "alarm_control_panel.test_alarm",
|
||||
ATTR_CODE: "1234",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
mock_concord232_client.arm.assert_called_once_with("stay", "silent")
|
||||
|
||||
|
||||
async def test_update_state_disarmed(
|
||||
hass: HomeAssistant, mock_concord232_client: MagicMock
|
||||
) -> None:
|
||||
"""Test update when alarm is disarmed."""
|
||||
mock_concord232_client.list_partitions.return_value = [{"arming_level": "Off"}]
|
||||
mock_concord232_client.list_zones.return_value = [
|
||||
{"number": 1, "name": "Zone 1", "state": "Normal"},
|
||||
]
|
||||
|
||||
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("alarm_control_panel.test_alarm")
|
||||
assert state.state == AlarmControlPanelState.DISARMED
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("arming_level", "expected_state"),
|
||||
[
|
||||
("Home", AlarmControlPanelState.ARMED_HOME),
|
||||
("Away", AlarmControlPanelState.ARMED_AWAY),
|
||||
],
|
||||
)
|
||||
async def test_update_state_armed(
|
||||
hass: HomeAssistant,
|
||||
mock_concord232_client: MagicMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
arming_level: str,
|
||||
expected_state: str,
|
||||
) -> None:
|
||||
"""Test update when alarm is armed."""
|
||||
mock_concord232_client.list_partitions.return_value = [
|
||||
{"arming_level": arming_level}
|
||||
]
|
||||
mock_concord232_client.partitions = (
|
||||
mock_concord232_client.list_partitions.return_value
|
||||
)
|
||||
mock_concord232_client.list_zones.return_value = [
|
||||
{"number": 1, "name": "Zone 1", "state": "Normal"},
|
||||
]
|
||||
|
||||
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Trigger update
|
||||
freezer.tick(10)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("alarm_control_panel.test_alarm")
|
||||
assert state.state == expected_state
|
||||
|
||||
|
||||
async def test_update_connection_error(
|
||||
hass: HomeAssistant,
|
||||
mock_concord232_client: MagicMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test update with connection error."""
|
||||
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_concord232_client.list_partitions.side_effect = (
|
||||
requests.exceptions.ConnectionError("Connection failed")
|
||||
)
|
||||
|
||||
freezer.tick(10)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert "Unable to connect to" in caplog.text
|
||||
|
||||
|
||||
async def test_update_no_partitions(
|
||||
hass: HomeAssistant,
|
||||
mock_concord232_client: MagicMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test update when no partitions are available."""
|
||||
mock_concord232_client.list_partitions.return_value = []
|
||||
|
||||
await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert "Concord232 reports no partitions" in caplog.text
|
||||
@@ -0,0 +1,201 @@
|
||||
"""Tests for the Concord232 binary sensor platform."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DOMAIN as BINARY_SENSOR_DOMAIN,
|
||||
BinarySensorDeviceClass,
|
||||
)
|
||||
from homeassistant.components.concord232.binary_sensor import (
|
||||
CONF_EXCLUDE_ZONES,
|
||||
CONF_ZONE_TYPES,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
VALID_CONFIG = {
|
||||
BINARY_SENSOR_DOMAIN: {
|
||||
"platform": "concord232",
|
||||
CONF_HOST: "localhost",
|
||||
CONF_PORT: 5007,
|
||||
}
|
||||
}
|
||||
|
||||
VALID_CONFIG_WITH_EXCLUDE = {
|
||||
BINARY_SENSOR_DOMAIN: {
|
||||
"platform": "concord232",
|
||||
CONF_HOST: "localhost",
|
||||
CONF_PORT: 5007,
|
||||
CONF_EXCLUDE_ZONES: [2],
|
||||
}
|
||||
}
|
||||
|
||||
VALID_CONFIG_WITH_ZONE_TYPES = {
|
||||
BINARY_SENSOR_DOMAIN: {
|
||||
"platform": "concord232",
|
||||
CONF_HOST: "localhost",
|
||||
CONF_PORT: 5007,
|
||||
CONF_ZONE_TYPES: {1: "door", 2: "window"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_setup_platform(
|
||||
hass: HomeAssistant, mock_concord232_client: MagicMock
|
||||
) -> None:
|
||||
"""Test platform setup."""
|
||||
await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state1 = hass.states.get("binary_sensor.zone_1")
|
||||
state2 = hass.states.get("binary_sensor.zone_2")
|
||||
assert state1 is not None
|
||||
assert state2 is not None
|
||||
assert state1.state == "off"
|
||||
assert state2.state == "off"
|
||||
|
||||
|
||||
async def test_setup_platform_connection_error(
|
||||
hass: HomeAssistant,
|
||||
mock_concord232_client: MagicMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test platform setup with connection error."""
|
||||
mock_concord232_client.list_zones.side_effect = requests.exceptions.ConnectionError(
|
||||
"Connection failed"
|
||||
)
|
||||
|
||||
await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert "Unable to connect to Concord232" in caplog.text
|
||||
assert hass.states.get("binary_sensor.zone_1") is None
|
||||
|
||||
|
||||
async def test_setup_with_exclude_zones(
|
||||
hass: HomeAssistant, mock_concord232_client: MagicMock
|
||||
) -> None:
|
||||
"""Test platform setup with excluded zones."""
|
||||
await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG_WITH_EXCLUDE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state1 = hass.states.get("binary_sensor.zone_1")
|
||||
state2 = hass.states.get("binary_sensor.zone_2")
|
||||
assert state1 is not None
|
||||
assert state2 is None # Zone 2 should be excluded
|
||||
|
||||
|
||||
async def test_setup_with_zone_types(
|
||||
hass: HomeAssistant, mock_concord232_client: MagicMock
|
||||
) -> None:
|
||||
"""Test platform setup with custom zone types."""
|
||||
await async_setup_component(
|
||||
hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG_WITH_ZONE_TYPES
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state1 = hass.states.get("binary_sensor.zone_1")
|
||||
state2 = hass.states.get("binary_sensor.zone_2")
|
||||
assert state1 is not None
|
||||
assert state2 is not None
|
||||
# Check device class is set correctly
|
||||
assert state1.attributes.get("device_class") == BinarySensorDeviceClass.DOOR
|
||||
assert state2.attributes.get("device_class") == BinarySensorDeviceClass.WINDOW
|
||||
|
||||
|
||||
async def test_zone_state_faulted(
|
||||
hass: HomeAssistant, mock_concord232_client: MagicMock
|
||||
) -> None:
|
||||
"""Test zone state when faulted."""
|
||||
mock_concord232_client.list_zones.return_value = [
|
||||
{"number": 1, "name": "Zone 1", "state": "Faulted"},
|
||||
]
|
||||
mock_concord232_client.zones = mock_concord232_client.list_zones.return_value
|
||||
|
||||
await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.zone_1")
|
||||
assert state.state == "on" # Faulted state means on (faulted)
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2023-10-21")
|
||||
async def test_zone_update_refresh(
|
||||
hass: HomeAssistant,
|
||||
mock_concord232_client: MagicMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test that zone updates refresh the client data."""
|
||||
mock_concord232_client.list_zones.return_value = [
|
||||
{"number": 1, "name": "Zone 1", "state": "Normal"},
|
||||
]
|
||||
mock_concord232_client.zones = mock_concord232_client.list_zones.return_value
|
||||
|
||||
await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("binary_sensor.zone_1").state == "off"
|
||||
|
||||
# Update zone state - need to update both return_value and zones attribute
|
||||
new_zones = [
|
||||
{"number": 1, "name": "Zone 1", "state": "Faulted"},
|
||||
]
|
||||
mock_concord232_client.list_zones.return_value = new_zones
|
||||
mock_concord232_client.zones = new_zones
|
||||
|
||||
freezer.tick(datetime.timedelta(seconds=10))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
freezer.tick(datetime.timedelta(seconds=10))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.zone_1")
|
||||
assert state.state == "on"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("sensor_name", "entity_id", "expected_device_class"),
|
||||
[
|
||||
(
|
||||
"MOTION Sensor",
|
||||
"binary_sensor.motion_sensor",
|
||||
BinarySensorDeviceClass.MOTION,
|
||||
),
|
||||
("SMOKE Sensor", "binary_sensor.smoke_sensor", BinarySensorDeviceClass.SMOKE),
|
||||
(
|
||||
"Unknown Sensor",
|
||||
"binary_sensor.unknown_sensor",
|
||||
BinarySensorDeviceClass.OPENING,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_device_class(
|
||||
hass: HomeAssistant,
|
||||
mock_concord232_client: MagicMock,
|
||||
sensor_name: str,
|
||||
entity_id: str,
|
||||
expected_device_class: BinarySensorDeviceClass,
|
||||
) -> None:
|
||||
"""Test zone type detection for motion sensor."""
|
||||
mock_concord232_client.list_zones.return_value = [
|
||||
{"number": 1, "name": sensor_name, "state": "Normal"},
|
||||
]
|
||||
mock_concord232_client.zones = mock_concord232_client.list_zones.return_value
|
||||
|
||||
await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.attributes.get("device_class") == expected_device_class
|
||||
Reference in New Issue
Block a user