From e08e8641cb960d3db84e573e0192dd7f0d4c7b7d Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 21 Oct 2024 19:33:32 +0200 Subject: [PATCH] Add diagnostics to Comelit SimpleHome (#128794) * Add diagnostics to Comelit SimpleHome * add test * add missing tests * introduce SnapshotAssertion * cleanup * exclude date based props --- .../components/comelit/diagnostics.py | 93 +++++++++++ tests/components/comelit/const.py | 79 +++++++++- .../comelit/snapshots/test_diagnostics.ambr | 144 ++++++++++++++++++ tests/components/comelit/test_diagnostics.py | 81 ++++++++++ 4 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/comelit/diagnostics.py create mode 100644 tests/components/comelit/snapshots/test_diagnostics.ambr create mode 100644 tests/components/comelit/test_diagnostics.py diff --git a/homeassistant/components/comelit/diagnostics.py b/homeassistant/components/comelit/diagnostics.py new file mode 100644 index 00000000000..afa57831eae --- /dev/null +++ b/homeassistant/components/comelit/diagnostics.py @@ -0,0 +1,93 @@ +"""Diagnostics support for Comelit integration.""" + +from __future__ import annotations + +from typing import Any + +from aiocomelit import ( + ComelitSerialBridgeObject, + ComelitVedoAreaObject, + ComelitVedoZoneObject, +) +from aiocomelit.const import BRIDGE + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_PIN, CONF_TYPE +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .coordinator import ComelitBaseCoordinator + +TO_REDACT = {CONF_PIN} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + + coordinator: ComelitBaseCoordinator = hass.data[DOMAIN][entry.entry_id] + + dev_list: list[dict[str, Any]] = [] + dev_type_list: list[dict[int, Any]] = [] + + for dev_type in coordinator.data: + dev_type_list = [] + for sensor_data in coordinator.data[dev_type].values(): + if isinstance(sensor_data, ComelitSerialBridgeObject): + dev_type_list.append( + { + sensor_data.index: { + "name": sensor_data.name, + "status": sensor_data.status, + "human_status": sensor_data.human_status, + "protected": sensor_data.protected, + "val": sensor_data.val, + "zone": sensor_data.zone, + "power": sensor_data.power, + "power_unit": sensor_data.power_unit, + } + } + ) + if isinstance(sensor_data, ComelitVedoAreaObject): + dev_type_list.append( + { + sensor_data.index: { + "name": sensor_data.name, + "human_status": sensor_data.human_status.value, + "p1": sensor_data.p1, + "p2": sensor_data.p2, + "ready": sensor_data.ready, + "armed": sensor_data.armed, + "alarm": sensor_data.alarm, + "alarm_memory": sensor_data.alarm_memory, + "sabotage": sensor_data.sabotage, + "anomaly": sensor_data.anomaly, + "in_time": sensor_data.in_time, + "out_time": sensor_data.out_time, + } + } + ) + if isinstance(sensor_data, ComelitVedoZoneObject): + dev_type_list.append( + { + sensor_data.index: { + "name": sensor_data.name, + "human_status": sensor_data.human_status.value, + "status": sensor_data.status, + "status_api": sensor_data.status_api, + } + } + ) + dev_list.append({dev_type: dev_type_list}) + + return { + "entry": async_redact_data(entry.as_dict(), TO_REDACT), + "type": entry.data.get(CONF_TYPE, BRIDGE), + "device_info": { + "last_update success": coordinator.last_update_success, + "last_exception": repr(coordinator.last_exception), + "devices": dev_list, + }, + } diff --git a/tests/components/comelit/const.py b/tests/components/comelit/const.py index 998c12c09b7..92fdfebfa1d 100644 --- a/tests/components/comelit/const.py +++ b/tests/components/comelit/const.py @@ -1,6 +1,19 @@ """Common stuff for Comelit SimpleHome tests.""" -from aiocomelit.const import VEDO +from aiocomelit import ComelitVedoAreaObject, ComelitVedoZoneObject +from aiocomelit.api import ComelitSerialBridgeObject +from aiocomelit.const import ( + CLIMATE, + COVER, + IRRIGATION, + LIGHT, + OTHER, + SCENARIO, + VEDO, + WATT, + AlarmAreaState, + AlarmZoneState, +) from homeassistant.components.comelit.const import DOMAIN from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PIN, CONF_PORT, CONF_TYPE @@ -27,3 +40,67 @@ MOCK_USER_BRIDGE_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0] MOCK_USER_VEDO_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][1] FAKE_PIN = 5678 + +BRIDGE_DEVICE_QUERY = { + CLIMATE: {}, + COVER: { + 0: ComelitSerialBridgeObject( + index=0, + name="Cover0", + status=0, + human_status="closed", + type="cover", + val=0, + protected=0, + zone="Open space", + power=0.0, + power_unit=WATT, + ) + }, + LIGHT: { + 0: ComelitSerialBridgeObject( + index=0, + name="Light0", + status=0, + human_status="off", + type="light", + val=0, + protected=0, + zone="Bathroom", + power=0.0, + power_unit=WATT, + ) + }, + OTHER: {}, + IRRIGATION: {}, + SCENARIO: {}, +} + +VEDO_DEVICE_QUERY = { + "aree": { + 0: ComelitVedoAreaObject( + index=0, + name="Area0", + p1=True, + p2=False, + ready=False, + armed=False, + alarm=False, + alarm_memory=False, + sabotage=False, + anomaly=False, + in_time=False, + out_time=False, + human_status=AlarmAreaState.UNKNOWN, + ) + }, + "zone": { + 0: ComelitVedoZoneObject( + index=0, + name="Zone0", + status_api="0x000", + status=0, + human_status=AlarmZoneState.REST, + ) + }, +} diff --git a/tests/components/comelit/snapshots/test_diagnostics.ambr b/tests/components/comelit/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..58ce74035f9 --- /dev/null +++ b/tests/components/comelit/snapshots/test_diagnostics.ambr @@ -0,0 +1,144 @@ +# serializer version: 1 +# name: test_entry_diagnostics_bridge + dict({ + 'device_info': dict({ + 'devices': list([ + dict({ + 'clima': list([ + ]), + }), + dict({ + 'shutter': list([ + dict({ + '0': dict({ + 'human_status': 'closed', + 'name': 'Cover0', + 'power': 0.0, + 'power_unit': 'W', + 'protected': 0, + 'status': 0, + 'val': 0, + 'zone': 'Open space', + }), + }), + ]), + }), + dict({ + 'light': list([ + dict({ + '0': dict({ + 'human_status': 'off', + 'name': 'Light0', + 'power': 0.0, + 'power_unit': 'W', + 'protected': 0, + 'status': 0, + 'val': 0, + 'zone': 'Bathroom', + }), + }), + ]), + }), + dict({ + 'other': list([ + ]), + }), + dict({ + 'irrigation': list([ + ]), + }), + dict({ + 'scenario': list([ + ]), + }), + ]), + 'last_exception': 'None', + 'last_update success': True, + }), + 'entry': dict({ + 'data': dict({ + 'host': 'fake_host', + 'pin': '**REDACTED**', + 'port': 80, + }), + 'disabled_by': None, + 'discovery_keys': dict({ + }), + 'domain': 'comelit', + 'minor_version': 1, + 'options': dict({ + }), + 'pref_disable_new_entities': False, + 'pref_disable_polling': False, + 'source': 'user', + 'title': 'Mock Title', + 'unique_id': None, + 'version': 1, + }), + 'type': 'Serial bridge', + }) +# --- +# name: test_entry_diagnostics_vedo + dict({ + 'device_info': dict({ + 'devices': list([ + dict({ + 'aree': list([ + dict({ + '0': dict({ + 'alarm': False, + 'alarm_memory': False, + 'anomaly': False, + 'armed': False, + 'human_status': 'unknown', + 'in_time': False, + 'name': 'Area0', + 'out_time': False, + 'p1': True, + 'p2': False, + 'ready': False, + 'sabotage': False, + }), + }), + ]), + }), + dict({ + 'zone': list([ + dict({ + '0': dict({ + 'human_status': 'rest', + 'name': 'Zone0', + 'status': 0, + 'status_api': '0x000', + }), + }), + ]), + }), + ]), + 'last_exception': 'None', + 'last_update success': True, + }), + 'entry': dict({ + 'data': dict({ + 'host': 'fake_vedo_host', + 'pin': '**REDACTED**', + 'port': 8080, + 'type': 'Vedo system', + }), + 'disabled_by': None, + 'discovery_keys': dict({ + }), + 'domain': 'comelit', + 'minor_version': 1, + 'options': dict({ + }), + 'pref_disable_new_entities': False, + 'pref_disable_polling': False, + 'source': 'user', + 'title': 'Mock Title', + 'unique_id': None, + 'version': 1, + }), + 'type': 'Vedo system', + }) +# --- diff --git a/tests/components/comelit/test_diagnostics.py b/tests/components/comelit/test_diagnostics.py new file mode 100644 index 00000000000..39d75af1152 --- /dev/null +++ b/tests/components/comelit/test_diagnostics.py @@ -0,0 +1,81 @@ +"""Tests for Comelit Simplehome diagnostics platform.""" + +from __future__ import annotations + +from unittest.mock import patch + +from syrupy import SnapshotAssertion +from syrupy.filters import props + +from homeassistant.components.comelit.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant + +from .const import ( + BRIDGE_DEVICE_QUERY, + MOCK_USER_BRIDGE_DATA, + MOCK_USER_VEDO_DATA, + VEDO_DEVICE_QUERY, +) + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.typing import ClientSessionGenerator + + +async def test_entry_diagnostics_bridge( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test Bridge config entry diagnostics.""" + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_BRIDGE_DATA) + entry.add_to_hass(hass) + + with ( + patch("aiocomelit.api.ComeliteSerialBridgeApi.login"), + patch( + "aiocomelit.api.ComeliteSerialBridgeApi.get_all_devices", + return_value=BRIDGE_DEVICE_QUERY, + ), + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ConfigEntryState.LOADED + assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot( + exclude=props( + "entry_id", + "created_at", + "modified_at", + ) + ) + + +async def test_entry_diagnostics_vedo( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test Vedo System config entry diagnostics.""" + entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_VEDO_DATA) + entry.add_to_hass(hass) + + with ( + patch("aiocomelit.api.ComelitVedoApi.login"), + patch( + "aiocomelit.api.ComelitVedoApi.get_all_areas_and_zones", + return_value=VEDO_DEVICE_QUERY, + ), + ): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ConfigEntryState.LOADED + assert await get_diagnostics_for_config_entry(hass, hass_client, entry) == snapshot( + exclude=props( + "entry_id", + "created_at", + "modified_at", + ) + )