diff --git a/homeassistant/components/diagnostics/__init__.py b/homeassistant/components/diagnostics/__init__.py index cb56dc79f03..84384a3843a 100644 --- a/homeassistant/components/diagnostics/__init__.py +++ b/homeassistant/components/diagnostics/__init__.py @@ -15,7 +15,9 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import integration_platform from homeassistant.helpers.device_registry import DeviceEntry, async_get from homeassistant.helpers.json import ExtendedJSONEncoder +from homeassistant.helpers.system_info import async_get_system_info from homeassistant.helpers.typing import ConfigType +from homeassistant.loader import DATA_CUSTOM_COMPONENTS, DATA_INTEGRATIONS, Integration from homeassistant.util.json import ( find_paths_unserializable_data, format_unserializable_data, @@ -121,17 +123,42 @@ def handle_get( ) -def _get_json_file_response( +async def _async_get_json_file_response( + hass: HomeAssistant, data: dict | list, filename: str, + domain: str, d_type: DiagnosticsType, d_id: str, sub_type: DiagnosticsSubType | None = None, sub_id: str | None = None, ) -> web.Response: """Return JSON file from dictionary.""" + hass_sys_info = await async_get_system_info(hass) + hass_sys_info["run_as_root"] = hass_sys_info["user"] == "root" + del hass_sys_info["user"] + + integration: Integration = hass.data[DATA_INTEGRATIONS][domain] + + custom_components = {} + cc_domain: str + cc_obj: Integration + for cc_domain, cc_obj in hass.data[DATA_CUSTOM_COMPONENTS].items(): + custom_components[cc_domain] = { + "version": cc_obj.manifest["version"], + "requirements": cc_obj.manifest["requirements"], + } try: - json_data = json.dumps(data, indent=2, cls=ExtendedJSONEncoder) + json_data = json.dumps( + { + "home_assistant": hass_sys_info, + "custom_components": custom_components, + "integration_manifest": integration.manifest, + "data": data, + }, + indent=2, + cls=ExtendedJSONEncoder, + ) except TypeError: _LOGGER.error( "Failed to serialize to JSON: %s/%s%s. Bad data at %s", @@ -189,7 +216,9 @@ class DownloadDiagnosticsView(http.HomeAssistantView): return web.Response(status=HTTPStatus.NOT_FOUND) data = await info[d_type.value](hass, config_entry) filename = f"{d_type}-{filename}" - return _get_json_file_response(data, filename, d_type.value, d_id) + return await _async_get_json_file_response( + hass, data, filename, config_entry.domain, d_type.value, d_id + ) # sub_type handling try: @@ -210,4 +239,6 @@ class DownloadDiagnosticsView(http.HomeAssistantView): return web.Response(status=HTTPStatus.NOT_FOUND) data = await info[sub_type.value](hass, config_entry, device) - return _get_json_file_response(data, filename, d_type, d_id, sub_type, sub_id) + return await _async_get_json_file_response( + hass, data, filename, config_entry.domain, d_type, d_id, sub_type, sub_id + ) diff --git a/tests/components/diagnostics/__init__.py b/tests/components/diagnostics/__init__.py index 69df193d284..3dbbb741a9f 100644 --- a/tests/components/diagnostics/__init__.py +++ b/tests/components/diagnostics/__init__.py @@ -4,7 +4,7 @@ from http import HTTPStatus from homeassistant.setup import async_setup_component -async def get_diagnostics_for_config_entry(hass, hass_client, config_entry): +async def _get_diagnostics_for_config_entry(hass, hass_client, config_entry): """Return the diagnostics config entry for the specified domain.""" assert await async_setup_component(hass, "diagnostics", {}) @@ -16,7 +16,13 @@ async def get_diagnostics_for_config_entry(hass, hass_client, config_entry): return await response.json() -async def get_diagnostics_for_device(hass, hass_client, config_entry, device): +async def get_diagnostics_for_config_entry(hass, hass_client, config_entry): + """Return the diagnostics config entry for the specified domain.""" + data = await _get_diagnostics_for_config_entry(hass, hass_client, config_entry) + return data["data"] + + +async def _get_diagnostics_for_device(hass, hass_client, config_entry, device): """Return the diagnostics for the specified device.""" assert await async_setup_component(hass, "diagnostics", {}) @@ -26,3 +32,9 @@ async def get_diagnostics_for_device(hass, hass_client, config_entry, device): ) assert response.status == HTTPStatus.OK return await response.json() + + +async def get_diagnostics_for_device(hass, hass_client, config_entry, device): + """Return the diagnostics for the specified device.""" + data = await _get_diagnostics_for_device(hass, hass_client, config_entry, device) + return data["data"] diff --git a/tests/components/diagnostics/test_init.py b/tests/components/diagnostics/test_init.py index 77b8d5fdebe..11b113e30f6 100644 --- a/tests/components/diagnostics/test_init.py +++ b/tests/components/diagnostics/test_init.py @@ -6,9 +6,10 @@ import pytest from homeassistant.components.websocket_api.const import TYPE_RESULT from homeassistant.helpers.device_registry import async_get +from homeassistant.helpers.system_info import async_get_system_info from homeassistant.setup import async_setup_component -from . import get_diagnostics_for_config_entry, get_diagnostics_for_device +from . import _get_diagnostics_for_config_entry, _get_diagnostics_for_device from tests.common import MockConfigEntry, mock_platform @@ -77,9 +78,22 @@ async def test_download_diagnostics(hass, hass_client): """Test download diagnostics.""" config_entry = MockConfigEntry(domain="fake_integration") config_entry.add_to_hass(hass) + hass_sys_info = await async_get_system_info(hass) + hass_sys_info["run_as_root"] = hass_sys_info["user"] == "root" + del hass_sys_info["user"] - assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { - "config_entry": "info" + assert await _get_diagnostics_for_config_entry(hass, hass_client, config_entry) == { + "home_assistant": hass_sys_info, + "custom_components": {}, + "integration_manifest": { + "codeowners": [], + "dependencies": [], + "domain": "fake_integration", + "is_built_in": True, + "name": "fake_integration", + "requirements": [], + }, + "data": {"config_entry": "info"}, } dev_reg = async_get(hass) @@ -87,9 +101,21 @@ async def test_download_diagnostics(hass, hass_client): config_entry_id=config_entry.entry_id, identifiers={("test", "test")} ) - assert await get_diagnostics_for_device( + assert await _get_diagnostics_for_device( hass, hass_client, config_entry, device - ) == {"device": "info"} + ) == { + "home_assistant": hass_sys_info, + "custom_components": {}, + "integration_manifest": { + "codeowners": [], + "dependencies": [], + "domain": "fake_integration", + "is_built_in": True, + "name": "fake_integration", + "requirements": [], + }, + "data": {"device": "info"}, + } async def test_failure_scenarios(hass, hass_client):