diff --git a/homeassistant/components/husqvarna_automower/diagnostics.py b/homeassistant/components/husqvarna_automower/diagnostics.py new file mode 100644 index 00000000000..7a715292a07 --- /dev/null +++ b/homeassistant/components/husqvarna_automower/diagnostics.py @@ -0,0 +1,46 @@ +"""Diagnostics support for Husqvarna Automower.""" +from __future__ import annotations + +import logging +from typing import Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceEntry + +from .const import DOMAIN +from .coordinator import AutomowerDataUpdateCoordinator + +CONF_REFRESH_TOKEN = "refresh_token" +POSITIONS = "positions" + +TO_REDACT = { + CONF_ACCESS_TOKEN, + CONF_REFRESH_TOKEN, + POSITIONS, +} +_LOGGER = logging.getLogger(__name__) + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + return async_redact_data(entry.as_dict(), TO_REDACT) + + +async def async_get_device_diagnostics( + hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry +) -> dict[str, Any]: + """Return diagnostics for a device entry.""" + coordinator: AutomowerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + for identifier in device.identifiers: + if identifier[0] == DOMAIN: + if ( + coordinator.data[identifier[1]].system.serial_number + == device.serial_number + ): + mower_id = identifier[1] + return async_redact_data(coordinator.data[mower_id].to_dict(), TO_REDACT) diff --git a/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr b/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..577d49cd026 --- /dev/null +++ b/tests/components/husqvarna_automower/snapshots/test_diagnostics.ambr @@ -0,0 +1,129 @@ +# serializer version: 1 +# name: test_device_diagnostics + dict({ + 'battery': dict({ + 'battery_percent': 100, + }), + 'calendar': dict({ + 'tasks': list([ + dict({ + 'duration': 300, + 'friday': True, + 'monday': True, + 'saturday': False, + 'start': 1140, + 'sunday': False, + 'thursday': False, + 'tuesday': False, + 'wednesday': True, + 'work_area_id': None, + }), + dict({ + 'duration': 480, + 'friday': False, + 'monday': False, + 'saturday': True, + 'start': 0, + 'sunday': False, + 'thursday': True, + 'tuesday': True, + 'wednesday': False, + 'work_area_id': None, + }), + ]), + }), + 'capabilities': dict({ + 'headlights': True, + 'position': True, + 'stay_out_zones': False, + 'work_areas': False, + }), + 'cutting_height': 4, + 'headlight': dict({ + 'mode': 'EVENING_ONLY', + }), + 'metadata': dict({ + 'connected': True, + 'status_dateteime': '2023-10-18T22:58:52.683000+00:00', + }), + 'mower': dict({ + 'activity': 'PARKED_IN_CS', + 'error_code': 0, + 'error_dateteime': None, + 'error_key': None, + 'mode': 'MAIN_AREA', + 'state': 'RESTRICTED', + }), + 'planner': dict({ + 'next_start_dateteime': '2023-06-05T19:00:00+00:00', + 'override': dict({ + 'action': 'NOT_ACTIVE', + }), + 'restricted_reason': 'WEEK_SCHEDULE', + }), + 'positions': '**REDACTED**', + 'statistics': dict({ + 'cutting_blade_usage_time': 123, + 'number_of_charging_cycles': 1380, + 'number_of_collisions': 11396, + 'total_charging_time': 4334400, + 'total_cutting_time': 4194000, + 'total_drive_distance': 1780272, + 'total_running_time': 4564800, + 'total_searching_time': 370800, + }), + 'stay_out_zones': dict({ + 'dirty': False, + 'zones': dict({ + '81C6EEA2-D139-4FEA-B134-F22A6B3EA403': dict({ + 'enabled': True, + 'name': 'Springflowers', + }), + }), + }), + 'system': dict({ + 'model': '450XH-TEST', + 'name': 'Test Mower 1', + 'serial_number': 123, + }), + 'work_areas': dict({ + '0': dict({ + 'cutting_height': 50, + 'name': None, + }), + '123456': dict({ + 'cutting_height': 50, + 'name': 'Front lawn', + }), + }), + }) +# --- +# name: test_entry_diagnostics + dict({ + 'data': dict({ + 'auth_implementation': 'husqvarna_automower', + 'token': dict({ + 'access_token': '**REDACTED**', + 'expires_at': 1709208000.0, + 'expires_in': 86399, + 'provider': 'husqvarna', + 'refresh_token': '**REDACTED**', + 'scope': 'iam:read amc:api', + 'token_type': 'Bearer', + 'user_id': '123', + }), + }), + 'disabled_by': None, + 'domain': 'husqvarna_automower', + 'entry_id': 'automower_test', + 'minor_version': 1, + 'options': dict({ + }), + 'pref_disable_new_entities': False, + 'pref_disable_polling': False, + 'source': 'user', + 'title': 'Husqvarna Automower of Erika Mustermann', + 'unique_id': '123', + 'version': 1, + }) +# --- diff --git a/tests/components/husqvarna_automower/test_diagnostics.py b/tests/components/husqvarna_automower/test_diagnostics.py new file mode 100644 index 00000000000..56eb0fdadde --- /dev/null +++ b/tests/components/husqvarna_automower/test_diagnostics.py @@ -0,0 +1,61 @@ +"""Test the Husqvarna Automower Diagnostics.""" +import datetime +from unittest.mock import AsyncMock + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.husqvarna_automower.const import DOMAIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr + +from .const import TEST_MOWER_ID + +from tests.common import MockConfigEntry +from tests.components.diagnostics import ( + get_diagnostics_for_config_entry, + get_diagnostics_for_device, +) +from tests.typing import ClientSessionGenerator + + +@pytest.mark.freeze_time(datetime.datetime(2024, 2, 29, 11, tzinfo=datetime.UTC)) +async def test_entry_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test config entry diagnostics.""" + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + result = await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) + assert result == snapshot + + +async def test_device_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, + mock_automower_client: AsyncMock, + mock_config_entry: MockConfigEntry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test select platform.""" + + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + reg_device = device_registry.async_get_device( + identifiers={(DOMAIN, TEST_MOWER_ID)}, + ) + assert reg_device is not None + result = await get_diagnostics_for_device( + hass, hass_client, mock_config_entry, reg_device + ) + assert result == snapshot