Add diagnostics for bosch alam integration (#142165)

* add diagnostics to bosch_alarm

* use snapshot
This commit is contained in:
Sanjay Govind 2025-04-03 23:05:08 +13:00 committed by GitHub
parent 934e81db43
commit 7a9a4db8d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 461 additions and 2 deletions

View File

@ -0,0 +1,73 @@
"""Diagnostics for bosch alarm."""
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant
from . import BoschAlarmConfigEntry
from .const import CONF_INSTALLER_CODE, CONF_USER_CODE
TO_REDACT = [CONF_INSTALLER_CODE, CONF_USER_CODE, CONF_PASSWORD]
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: BoschAlarmConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
return {
"entry_data": async_redact_data(entry.data, TO_REDACT),
"data": {
"model": entry.runtime_data.model,
"serial_number": entry.runtime_data.serial_number,
"protocol_version": entry.runtime_data.protocol_version,
"firmware_version": entry.runtime_data.firmware_version,
"areas": [
{
"id": area_id,
"name": area.name,
"all_ready": area.all_ready,
"part_ready": area.part_ready,
"faults": area.faults,
"alarms": area.alarms,
"disarmed": area.is_disarmed(),
"arming": area.is_arming(),
"pending": area.is_pending(),
"part_armed": area.is_part_armed(),
"all_armed": area.is_all_armed(),
"armed": area.is_armed(),
"triggered": area.is_triggered(),
}
for area_id, area in entry.runtime_data.areas.items()
],
"points": [
{
"id": point_id,
"name": point.name,
"open": point.is_open(),
"normal": point.is_normal(),
}
for point_id, point in entry.runtime_data.points.items()
],
"doors": [
{
"id": door_id,
"name": door.name,
"open": door.is_open(),
"locked": door.is_locked(),
}
for door_id, door in entry.runtime_data.doors.items()
],
"outputs": [
{
"id": output_id,
"name": output.name,
"active": output.is_active(),
}
for output_id, output in entry.runtime_data.outputs.items()
],
"history_events": entry.runtime_data.events,
},
}

View File

@ -4,7 +4,7 @@ from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock, patch
from bosch_alarm_mode2.panel import Area
from bosch_alarm_mode2.panel import Area, Door, Output, Point
from bosch_alarm_mode2.utils import Observable
import pytest
@ -78,14 +78,65 @@ def mock_setup_entry() -> Generator[AsyncMock]:
yield mock_setup_entry
@pytest.fixture
def points() -> Generator[dict[int, Point]]:
"""Define a mocked door."""
names = [
"Window",
"Door",
"Motion Detector",
"CO Detector",
"Smoke Detector",
"Glassbreak Sensor",
"Bedroom",
]
points = {}
for i, name in enumerate(names):
mock = AsyncMock(spec=Point)
mock.name = name
mock.status_observer = AsyncMock(spec=Observable)
mock.is_open.return_value = False
mock.is_normal.return_value = True
points[i] = mock
return points
@pytest.fixture
def output() -> Generator[Output]:
"""Define a mocked output."""
mock = AsyncMock(spec=Output)
mock.name = "Output A"
mock.status_observer = AsyncMock(spec=Observable)
mock.is_active.return_value = False
return mock
@pytest.fixture
def door() -> Generator[Door]:
"""Define a mocked door."""
mock = AsyncMock(spec=Door)
mock.name = "Main Door"
mock.status_observer = AsyncMock(spec=Observable)
mock.is_open.return_value = False
mock.is_locked.return_value = True
return mock
@pytest.fixture
def area() -> Generator[Area]:
"""Define a mocked area."""
mock = AsyncMock(spec=Area)
mock.name = "Area1"
mock.status_observer = AsyncMock(spec=Observable)
mock.alarm_observer = AsyncMock(spec=Observable)
mock.ready_observer = AsyncMock(spec=Observable)
mock.alarms = []
mock.faults = []
mock.all_ready = True
mock.part_ready = True
mock.is_triggered.return_value = False
mock.is_disarmed.return_value = True
mock.is_armed.return_value = False
mock.is_arming.return_value = False
mock.is_pending.return_value = False
mock.is_part_armed.return_value = False
@ -95,7 +146,12 @@ def area() -> Generator[Area]:
@pytest.fixture
def mock_panel(
area: AsyncMock, model_name: str, serial_number: str | None
area: AsyncMock,
door: AsyncMock,
output: AsyncMock,
points: dict[int, AsyncMock],
model_name: str,
serial_number: str | None,
) -> Generator[AsyncMock]:
"""Define a fixture to set up Bosch Alarm."""
with (
@ -106,10 +162,18 @@ def mock_panel(
):
client = mock_panel.return_value
client.areas = {1: area}
client.doors = {1: door}
client.outputs = {1: output}
client.points = points
client.model = model_name
client.faults = []
client.events = []
client.firmware_version = "1.0.0"
client.protocol_version = "1.0.0"
client.serial_number = serial_number
client.connection_status_observer = AsyncMock(spec=Observable)
client.faults_observer = AsyncMock(spec=Observable)
client.history_observer = AsyncMock(spec=Observable)
yield client

View File

@ -0,0 +1,290 @@
# serializer version: 1
# name: test_diagnostics[amax_3000]
dict({
'data': dict({
'areas': list([
dict({
'alarms': list([
]),
'all_armed': False,
'all_ready': True,
'armed': False,
'arming': False,
'disarmed': True,
'faults': list([
]),
'id': 1,
'name': 'Area1',
'part_armed': False,
'part_ready': True,
'pending': False,
'triggered': False,
}),
]),
'doors': list([
dict({
'id': 1,
'locked': True,
'name': 'Main Door',
'open': False,
}),
]),
'firmware_version': '1.0.0',
'history_events': list([
]),
'model': 'AMAX 3000',
'outputs': list([
dict({
'active': False,
'id': 1,
'name': 'Output A',
}),
]),
'points': list([
dict({
'id': 0,
'name': 'Window',
'normal': True,
'open': False,
}),
dict({
'id': 1,
'name': 'Door',
'normal': True,
'open': False,
}),
dict({
'id': 2,
'name': 'Motion Detector',
'normal': True,
'open': False,
}),
dict({
'id': 3,
'name': 'CO Detector',
'normal': True,
'open': False,
}),
dict({
'id': 4,
'name': 'Smoke Detector',
'normal': True,
'open': False,
}),
dict({
'id': 5,
'name': 'Glassbreak Sensor',
'normal': True,
'open': False,
}),
dict({
'id': 6,
'name': 'Bedroom',
'normal': True,
'open': False,
}),
]),
'protocol_version': '1.0.0',
'serial_number': None,
}),
'entry_data': dict({
'host': '0.0.0.0',
'installer_code': '**REDACTED**',
'model': 'AMAX 3000',
'password': '**REDACTED**',
'port': 7700,
}),
})
# ---
# name: test_diagnostics[b5512]
dict({
'data': dict({
'areas': list([
dict({
'alarms': list([
]),
'all_armed': False,
'all_ready': True,
'armed': False,
'arming': False,
'disarmed': True,
'faults': list([
]),
'id': 1,
'name': 'Area1',
'part_armed': False,
'part_ready': True,
'pending': False,
'triggered': False,
}),
]),
'doors': list([
dict({
'id': 1,
'locked': True,
'name': 'Main Door',
'open': False,
}),
]),
'firmware_version': '1.0.0',
'history_events': list([
]),
'model': 'B5512 (US1B)',
'outputs': list([
dict({
'active': False,
'id': 1,
'name': 'Output A',
}),
]),
'points': list([
dict({
'id': 0,
'name': 'Window',
'normal': True,
'open': False,
}),
dict({
'id': 1,
'name': 'Door',
'normal': True,
'open': False,
}),
dict({
'id': 2,
'name': 'Motion Detector',
'normal': True,
'open': False,
}),
dict({
'id': 3,
'name': 'CO Detector',
'normal': True,
'open': False,
}),
dict({
'id': 4,
'name': 'Smoke Detector',
'normal': True,
'open': False,
}),
dict({
'id': 5,
'name': 'Glassbreak Sensor',
'normal': True,
'open': False,
}),
dict({
'id': 6,
'name': 'Bedroom',
'normal': True,
'open': False,
}),
]),
'protocol_version': '1.0.0',
'serial_number': None,
}),
'entry_data': dict({
'host': '0.0.0.0',
'model': 'B5512 (US1B)',
'password': '**REDACTED**',
'port': 7700,
}),
})
# ---
# name: test_diagnostics[solution_3000]
dict({
'data': dict({
'areas': list([
dict({
'alarms': list([
]),
'all_armed': False,
'all_ready': True,
'armed': False,
'arming': False,
'disarmed': True,
'faults': list([
]),
'id': 1,
'name': 'Area1',
'part_armed': False,
'part_ready': True,
'pending': False,
'triggered': False,
}),
]),
'doors': list([
dict({
'id': 1,
'locked': True,
'name': 'Main Door',
'open': False,
}),
]),
'firmware_version': '1.0.0',
'history_events': list([
]),
'model': 'Solution 3000',
'outputs': list([
dict({
'active': False,
'id': 1,
'name': 'Output A',
}),
]),
'points': list([
dict({
'id': 0,
'name': 'Window',
'normal': True,
'open': False,
}),
dict({
'id': 1,
'name': 'Door',
'normal': True,
'open': False,
}),
dict({
'id': 2,
'name': 'Motion Detector',
'normal': True,
'open': False,
}),
dict({
'id': 3,
'name': 'CO Detector',
'normal': True,
'open': False,
}),
dict({
'id': 4,
'name': 'Smoke Detector',
'normal': True,
'open': False,
}),
dict({
'id': 5,
'name': 'Glassbreak Sensor',
'normal': True,
'open': False,
}),
dict({
'id': 6,
'name': 'Bedroom',
'normal': True,
'open': False,
}),
]),
'protocol_version': '1.0.0',
'serial_number': '1234567890',
}),
'entry_data': dict({
'host': '0.0.0.0',
'model': 'Solution 3000',
'port': 7700,
'user_code': '**REDACTED**',
}),
})
# ---

View File

@ -0,0 +1,32 @@
"""Test the Bosch Alarm diagnostics."""
from typing import Any
from unittest.mock import AsyncMock
from syrupy.assertion import SnapshotAssertion
from homeassistant.core import HomeAssistant
from . import setup_integration
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
async def test_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
mock_panel: AsyncMock,
area: AsyncMock,
model_name: str,
serial_number: str,
snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
config_flow_data: dict[str, Any],
) -> None:
"""Test generating diagnostics for bosch alarm."""
await setup_integration(hass, mock_config_entry)
diag = await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry)
assert diag == snapshot