From 962d5386c728cb6193a6f0de8fbbb1dc9e26fc2b Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Tue, 31 Mar 2026 22:35:09 +0300 Subject: [PATCH] Add diagnostics to Anthropic integration (#166739) --- .../components/anthropic/diagnostics.py | 64 ++++++++++++++ .../components/anthropic/quality_scale.yaml | 2 +- .../anthropic/snapshots/test_diagnostics.ambr | 87 +++++++++++++++++++ .../components/anthropic/test_diagnostics.py | 44 ++++++++++ 4 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/anthropic/diagnostics.py create mode 100644 tests/components/anthropic/snapshots/test_diagnostics.ambr create mode 100644 tests/components/anthropic/test_diagnostics.py diff --git a/homeassistant/components/anthropic/diagnostics.py b/homeassistant/components/anthropic/diagnostics.py new file mode 100644 index 00000000000..d5985f10b1d --- /dev/null +++ b/homeassistant/components/anthropic/diagnostics.py @@ -0,0 +1,64 @@ +"""Diagnostics support for Anthropic.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from anthropic import __title__, __version__ + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.const import CONF_API_KEY +from homeassistant.helpers import entity_registry as er + +from .const import ( + CONF_PROMPT, + CONF_WEB_SEARCH_CITY, + CONF_WEB_SEARCH_COUNTRY, + CONF_WEB_SEARCH_REGION, + CONF_WEB_SEARCH_TIMEZONE, +) + +if TYPE_CHECKING: + from homeassistant.core import HomeAssistant + + from . import AnthropicConfigEntry + + +TO_REDACT = { + CONF_API_KEY, + CONF_PROMPT, + CONF_WEB_SEARCH_CITY, + CONF_WEB_SEARCH_REGION, + CONF_WEB_SEARCH_COUNTRY, + CONF_WEB_SEARCH_TIMEZONE, +} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: AnthropicConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + + return { + "client": f"{__title__}=={__version__}", + "title": entry.title, + "entry_id": entry.entry_id, + "entry_version": f"{entry.version}.{entry.minor_version}", + "state": entry.state.value, + "data": async_redact_data(entry.data, TO_REDACT), + "options": async_redact_data(entry.options, TO_REDACT), + "subentries": { + subentry.subentry_id: { + "title": subentry.title, + "subentry_type": subentry.subentry_type, + "data": async_redact_data(subentry.data, TO_REDACT), + } + for subentry in entry.subentries.values() + }, + "entities": { + entity_entry.entity_id: entity_entry.extended_dict + for entity_entry in er.async_entries_for_config_entry( + er.async_get(hass), entry.entry_id + ) + }, + } diff --git a/homeassistant/components/anthropic/quality_scale.yaml b/homeassistant/components/anthropic/quality_scale.yaml index 8d62ea26f47..1cf9008fc41 100644 --- a/homeassistant/components/anthropic/quality_scale.yaml +++ b/homeassistant/components/anthropic/quality_scale.yaml @@ -46,7 +46,7 @@ rules: test-coverage: done # Gold devices: done - diagnostics: todo + diagnostics: done discovery-update-info: status: exempt comment: | diff --git a/tests/components/anthropic/snapshots/test_diagnostics.ambr b/tests/components/anthropic/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000..5ca08252b33 --- /dev/null +++ b/tests/components/anthropic/snapshots/test_diagnostics.ambr @@ -0,0 +1,87 @@ +# serializer version: 1 +# name: test_entry_diagnostics + dict({ + 'data': dict({ + 'api_key': '**REDACTED**', + }), + 'entities': dict({ + 'ai_task.claude_ai_task': dict({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'categories': dict({ + }), + 'device_class': None, + 'disabled_by': None, + 'entity_category': None, + 'entity_id': 'ai_task.claude_ai_task', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'conversation': dict({ + 'should_expose': False, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'anthropic', + 'translation_key': 'ai_task_data', + }), + 'conversation.claude_conversation': dict({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'categories': dict({ + }), + 'device_class': None, + 'disabled_by': None, + 'entity_category': None, + 'entity_id': 'conversation.claude_conversation', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'labels': list([ + ]), + 'name': None, + 'options': dict({ + 'conversation': dict({ + 'should_expose': False, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'anthropic', + 'translation_key': 'conversation', + }), + }), + 'entry_version': '2.3', + 'options': dict({ + }), + 'state': 'loaded', + 'subentries': list([ + dict({ + 'data': dict({ + }), + 'subentry_type': 'conversation', + 'title': 'Claude conversation', + }), + dict({ + 'data': dict({ + }), + 'subentry_type': 'ai_task_data', + 'title': 'Claude AI Task', + }), + ]), + 'title': 'Claude', + }) +# --- diff --git a/tests/components/anthropic/test_diagnostics.py b/tests/components/anthropic/test_diagnostics.py new file mode 100644 index 00000000000..44b6bdc73c0 --- /dev/null +++ b/tests/components/anthropic/test_diagnostics.py @@ -0,0 +1,44 @@ +"""Test Anthropic diagnostics.""" + +from syrupy.assertion import SnapshotAssertion + +from homeassistant.core import HomeAssistant + +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( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + mock_config_entry: MockConfigEntry, + mock_init_component: None, + snapshot: SnapshotAssertion, +) -> None: + """Test config entry diagnostics.""" + diagnostics = await get_diagnostics_for_config_entry( + hass, hass_client, mock_config_entry + ) + # Remove non-deterministic values from diagnostics. + assert diagnostics.pop("client").startswith("anthropic==") + diagnostics.pop("entry_id") + subentries = diagnostics.pop("subentries") + diagnostics["subentries"] = [ + subentry for subentry_id, subentry in subentries.items() + ] + for entity_id, entity in diagnostics["entities"].copy().items(): + for key in ( + "config_entry_id", + "config_subentry_id", + "created_at", + "device_id", + "id", + "modified_at", + "unique_id", + ): + if key in entity: + entity.pop(key) + diagnostics["entities"][entity_id] = entity + + assert diagnostics == snapshot