Add diagnostics integration (#64330)

This commit is contained in:
Paulus Schoutsen 2022-01-17 20:42:18 -08:00 committed by GitHub
parent a334e0c7b9
commit 6055cd20c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 220 additions and 0 deletions

View file

@ -208,6 +208,8 @@ tests/components/dexcom/* @gagebenne
homeassistant/components/dhcp/* @bdraco
tests/components/dhcp/* @bdraco
homeassistant/components/dht/* @thegardenmonkey
homeassistant/components/diagnostics/* @home-assistant/core
tests/components/diagnostics/* @home-assistant/core
homeassistant/components/digital_ocean/* @fabaff
homeassistant/components/discogs/* @thibmaek
homeassistant/components/dlna_dmr/* @StevenLooman @chishm

View file

@ -7,6 +7,7 @@
"cloud",
"counter",
"dhcp",
"diagnostics",
"energy",
"frontend",
"history",

View file

@ -0,0 +1,123 @@
"""The Diagnostics integration."""
from __future__ import annotations
import json
import logging
from typing import Protocol
from aiohttp import web
import voluptuous as vol
from homeassistant.components import http, websocket_api
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import integration_platform
from homeassistant.helpers.json import ExtendedJSONEncoder
from homeassistant.util.json import (
find_paths_unserializable_data,
format_unserializable_data,
)
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
"""Set up Diagnostics from a config entry."""
hass.data[DOMAIN] = {}
await integration_platform.async_process_integration_platforms(
hass, DOMAIN, _register_diagnostics_platform
)
websocket_api.async_register_command(hass, handle_info)
hass.http.register_view(DownloadDiagnosticsView)
return True
class DiagnosticsProtocol(Protocol):
"""Define the format that diagnostics platforms can have."""
async def async_get_config_entry_diagnostics(
self, hass: HomeAssistant, config_entry: ConfigEntry
) -> dict:
"""Return diagnostics for a config entry."""
async def _register_diagnostics_platform(
hass: HomeAssistant, integration_domain: str, platform: DiagnosticsProtocol
):
"""Register a diagnostics platform."""
hass.data[DOMAIN][integration_domain] = {
"config_entry": getattr(platform, "async_get_config_entry_diagnostics", None)
}
@websocket_api.require_admin
@websocket_api.websocket_command({vol.Required("type"): "diagnostics/list"})
@callback
def handle_info(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
):
"""List all possible diagnostic handlers."""
connection.send_result(
msg["id"],
[
{
"domain": domain,
"handlers": {key: val is not None for key, val in info.items()},
}
for domain, info in hass.data[DOMAIN].items()
],
)
class DownloadDiagnosticsView(http.HomeAssistantView):
"""Download diagnostics view."""
url = "/api/diagnostics/{d_type}/{d_id}"
name = "api:diagnostics"
async def get( # pylint: disable=no-self-use
self, request: web.Request, d_type: str, d_id: str
) -> web.Response:
"""Download diagnostics."""
if d_type != "config_entry":
return web.Response(status=404)
hass = request.app["hass"]
config_entry = hass.config_entries.async_get_entry(d_id)
if config_entry is None:
return web.Response(status=404)
info = hass.data[DOMAIN].get(config_entry.domain)
if info is None:
return web.Response(status=404)
if info["config_entry"] is None:
return web.Response(status=404)
data = await info["config_entry"](hass, config_entry)
try:
json_data = json.dumps(data, indent=4, cls=ExtendedJSONEncoder)
except TypeError:
_LOGGER.error(
"Failed to serialize to JSON: %s/%s. Bad data at %s",
d_type,
d_id,
format_unserializable_data(find_paths_unserializable_data(data)),
)
return web.Response(status=500)
return web.Response(
body=json_data,
content_type="application/json",
headers={
"Content-Disposition": f'attachment; filename="{config_entry.domain}-{config_entry.entry_id}.json"'
},
)

View file

@ -0,0 +1,3 @@
"""Constants for the Diagnostics integration."""
DOMAIN = "diagnostics"

View file

@ -0,0 +1,9 @@
{
"domain": "diagnostics",
"name": "Diagnostics",
"config_flow": false,
"documentation": "https://www.home-assistant.io/integrations/diagnostics",
"dependencies": ["http"],
"codeowners": ["@home-assistant/core"],
"quality_scale": "internal"
}

View file

@ -0,0 +1,3 @@
{
"title": "Diagnostics"
}

View file

@ -0,0 +1,21 @@
{
"config": {
"abort": {
"already_configured": "Device is already configured"
},
"error": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"unknown": "Unexpected error"
},
"step": {
"user": {
"data": {
"host": "Host",
"password": "Password",
"username": "Username"
}
}
}
}
}

View file

@ -52,6 +52,7 @@ NO_IOT_CLASS = [
"default_config",
"device_automation",
"device_tracker",
"diagnostics",
"discovery",
"downloader",
"fan",

View file

@ -0,0 +1 @@
"""Tests for the Diagnostics integration."""

View file

@ -0,0 +1,56 @@
"""Test the Diagnostics integration."""
from http import HTTPStatus
from unittest.mock import AsyncMock, Mock
import pytest
from homeassistant.components.websocket_api.const import TYPE_RESULT
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry, mock_platform
@pytest.fixture(autouse=True)
async def mock_diagnostics_integration(hass):
"""Mock a diagnostics integration."""
hass.config.components.add("fake_integration")
mock_platform(
hass,
"fake_integration.diagnostics",
Mock(
async_get_config_entry_diagnostics=AsyncMock(
return_value={
"hello": "info",
}
),
),
)
assert await async_setup_component(hass, "diagnostics", {})
async def test_websocket_info(hass, hass_ws_client):
"""Test camera_thumbnail websocket command."""
client = await hass_ws_client(hass)
await client.send_json({"id": 5, "type": "diagnostics/list"})
msg = await client.receive_json()
assert msg["id"] == 5
assert msg["type"] == TYPE_RESULT
assert msg["success"]
assert msg["result"] == [
{"domain": "fake_integration", "handlers": {"config_entry": True}}
]
async def test_download_diagnostics(hass, hass_client):
"""Test record service."""
config_entry = MockConfigEntry(domain="fake_integration")
config_entry.add_to_hass(hass)
client = await hass_client()
response = await client.get(
f"/api/diagnostics/config_entry/{config_entry.entry_id}"
)
assert response.status == HTTPStatus.OK
assert await response.json() == {"hello": "info"}