diff --git a/homeassistant/components/totalconnect/diagnostics.py b/homeassistant/components/totalconnect/diagnostics.py new file mode 100644 index 00000000000..4a9a73c89a9 --- /dev/null +++ b/homeassistant/components/totalconnect/diagnostics.py @@ -0,0 +1,113 @@ +"""Provides diagnostics for TotalConnect.""" +from __future__ import annotations + +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN + +TO_REDACT = [ + "username", + "Password", + "Usercode", + "UserID", + "Serial Number", + "serial_number", + "sensor_serial_number", +] + +# Private variable access needed for diagnostics +# pylint: disable=protected-access + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, config_entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + client = hass.data[DOMAIN][config_entry.entry_id].client + + data: dict[str, Any] = {} + data["client"] = { + "auto_bypass_low_battery": client.auto_bypass_low_battery, + "module_flags": client._module_flags, + "retry_delay": client.retry_delay, + "invalid_credentials": client._invalid_credentials, + } + + data["user"] = { + "master": client._user._master_user, + "user_admin": client._user._user_admin, + "config_admin": client._user._config_admin, + "security_problem": client._user.security_problem(), + "features": client._user._features, + } + + data["locations"] = [] + for location in client.locations.values(): + new_location = { + "location_id": location.location_id, + "name": location.location_name, + "module_flags": location._module_flags, + "security_device_id": location.security_device_id, + "ac_loss": location.ac_loss, + "low_battery": location.low_battery, + "auto_bypass_low_battery": location.auto_bypass_low_battery, + "cover_tampered": location.cover_tampered, + "arming_state": location.arming_state, + } + + new_location["devices"] = [] + for device in location.devices.values(): + new_device = { + "device_id": device.deviceid, + "name": device.name, + "class_id": device.class_id, + "serial_number": device.serial_number, + "security_panel_type_id": device.security_panel_type_id, + "serial_text": device.serial_text, + "flags": device.flags, + } + new_location["devices"].append(new_device) + + new_location["partitions"] = [] + for partition in location.partitions.values(): + new_partition = { + "partition_id": partition.partitionid, + "name": partition.name, + "is_stay_armed": partition.is_stay_armed, + "is_fire_enabled": partition.is_fire_enabled, + "is_common_enabled": partition.is_common_enabled, + "is_locked": partition.is_locked, + "is_new_partition": partition.is_new_partition, + "is_night_stay_enabled": partition.is_night_stay_enabled, + "exit_delay_timer": partition.exit_delay_timer, + } + new_location["partitions"].append(new_partition) + + new_location["zones"] = [] + for zone in location.zones.values(): + new_zone = { + "zone_id": zone.zoneid, + "description": zone.description, + "partition": zone.partition, + "status": zone.status, + "zone_type_id": zone.zone_type_id, + "can_be_bypassed": zone.can_be_bypassed, + "battery_level": zone.battery_level, + "signal_strength": zone.signal_strength, + "sensor_serial_number": zone.sensor_serial_number, + "loop_number": zone.loop_number, + "response_type": zone.response_type, + "alarm_report_state": zone.alarm_report_state, + "supervision_type": zone.supervision_type, + "chime_state": zone.chime_state, + "device_type": zone.device_type, + } + new_location["zones"].append(new_zone) + + data["locations"].append(new_location) + + return async_redact_data(data, TO_REDACT) diff --git a/tests/components/totalconnect/common.py b/tests/components/totalconnect/common.py index ec0c182895f..65b10718fd5 100644 --- a/tests/components/totalconnect/common.py +++ b/tests/components/totalconnect/common.py @@ -345,3 +345,28 @@ async def setup_platform(hass, platform): await hass.async_block_till_done() return mock_entry + + +async def init_integration(hass): + """Set up the TotalConnect integration.""" + # first set up a config entry and add it to hass + mock_entry = MockConfigEntry(domain=DOMAIN, data=CONFIG_DATA) + mock_entry.add_to_hass(hass) + + responses = [ + RESPONSE_AUTHENTICATE, + RESPONSE_PARTITION_DETAILS, + RESPONSE_GET_ZONE_DETAILS_SUCCESS, + RESPONSE_DISARMED, + RESPONSE_DISARMED, + ] + + with patch( + TOTALCONNECT_REQUEST, + side_effect=responses, + ) as mock_request: + await hass.config_entries.async_setup(mock_entry.entry_id) + assert mock_request.call_count == 5 + await hass.async_block_till_done() + + return mock_entry diff --git a/tests/components/totalconnect/test_diagnostics.py b/tests/components/totalconnect/test_diagnostics.py new file mode 100644 index 00000000000..9c6b1975097 --- /dev/null +++ b/tests/components/totalconnect/test_diagnostics.py @@ -0,0 +1,32 @@ +"""Test TotalConnect diagnostics.""" + +from homeassistant.components.diagnostics import REDACTED + +from .common import LOCATION_ID, init_integration + +from tests.components.diagnostics import get_diagnostics_for_config_entry + + +async def test_entry_diagnostics(hass, hass_client): + """Test config entry diagnostics.""" + entry = await init_integration(hass) + + result = await get_diagnostics_for_config_entry(hass, hass_client, entry) + + client = result["client"] + assert client["invalid_credentials"] is False + + user = result["user"] + assert user["master"] is False + + location = result["locations"][0] + assert location["location_id"] == LOCATION_ID + + device = location["devices"][0] + assert device["serial_number"] == REDACTED + + partition = location["partitions"][0] + assert partition["name"] == "Test1" + + zone = location["zones"][0] + assert zone["zone_id"] == "1"