diff --git a/homeassistant/components/logger/helpers.py b/homeassistant/components/logger/helpers.py index 19afe18e3fe9..ec06701f5b38 100644 --- a/homeassistant/components/logger/helpers.py +++ b/homeassistant/components/logger/helpers.py @@ -181,6 +181,16 @@ class LoggerSettings: """Save settings.""" self._store.async_delay_save(self._async_data_to_save, delay) + @callback + def async_get_integration_domains(self) -> set[str]: + """Get domains that have integration-level log settings.""" + stored_log_config = self._stored_config[STORAGE_LOG_KEY] + return { + domain + for domain, setting in stored_log_config.items() + if setting.type == LogSettingsType.INTEGRATION + } + @callback def _async_get_logger_logs(self) -> dict[str, int]: """Get the logger logs.""" diff --git a/homeassistant/components/logger/websocket_api.py b/homeassistant/components/logger/websocket_api.py index 041fe417698c..660bdf4c599a 100644 --- a/homeassistant/components/logger/websocket_api.py +++ b/homeassistant/components/logger/websocket_api.py @@ -6,6 +6,7 @@ import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.components.websocket_api import ActiveConnection +from homeassistant.config_entries import DISCOVERY_SOURCES from homeassistant.core import HomeAssistant, callback from homeassistant.loader import IntegrationNotFound, async_get_integration from homeassistant.setup import async_get_loaded_integrations @@ -34,6 +35,16 @@ def handle_integration_log_info( hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Handle integrations logger info.""" + integrations = set(async_get_loaded_integrations(hass)) + + # Add discovered config flows that are not yet loaded + for flow in hass.config_entries.flow.async_progress(): + if flow["context"].get("source") in DISCOVERY_SOURCES: + integrations.add(flow["handler"]) + + # Add integrations with custom log settings + integrations.update(hass.data[DATA_LOGGER].settings.async_get_integration_domains()) + connection.send_result( msg["id"], [ @@ -43,7 +54,7 @@ def handle_integration_log_info( f"homeassistant.components.{integration}" ).getEffectiveLevel(), } - for integration in async_get_loaded_integrations(hass) + for integration in integrations ], ) diff --git a/tests/components/logger/test_websocket_api.py b/tests/components/logger/test_websocket_api.py index debe26576bdf..91bddd2f414d 100644 --- a/tests/components/logger/test_websocket_api.py +++ b/tests/components/logger/test_websocket_api.py @@ -3,13 +3,19 @@ import logging from unittest.mock import patch -from homeassistant import loader +from homeassistant import config_entries, loader from homeassistant.components.logger.helpers import DATA_LOGGER from homeassistant.components.websocket_api import TYPE_RESULT from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component -from tests.common import MockUser +from tests.common import ( + MockModule, + MockUser, + mock_config_flow, + mock_integration, + mock_platform, +) from tests.typing import WebSocketGenerator @@ -33,6 +39,105 @@ async def test_integration_log_info( assert {"domain": "http", "level": logging.DEBUG} in msg["result"] +async def test_integration_log_info_discovered_flows( + hass: HomeAssistant, hass_ws_client: WebSocketGenerator, hass_admin_user: MockUser +) -> None: + """Test that log info includes discovered flows.""" + assert await async_setup_component(hass, "logger", {}) + + # Set up a discovery flow (zeroconf) + mock_integration(hass, MockModule("discovered_integration")) + mock_platform(hass, "discovered_integration.config_flow", None) + + class DiscoveryFlow(config_entries.ConfigFlow, domain="discovered_integration"): + """Test discovery flow.""" + + VERSION = 1 + + async def async_step_zeroconf(self, discovery_info=None): + """Test zeroconf step.""" + return self.async_show_form(step_id="zeroconf") + + # Set up a user flow (non-discovery) + mock_integration(hass, MockModule("user_flow_integration")) + mock_platform(hass, "user_flow_integration.config_flow", None) + + class UserFlow(config_entries.ConfigFlow, domain="user_flow_integration"): + """Test user flow.""" + + VERSION = 1 + + async def async_step_user(self, user_input=None): + """Test user step.""" + return self.async_show_form(step_id="user") + + with ( + mock_config_flow("discovered_integration", DiscoveryFlow), + mock_config_flow("user_flow_integration", UserFlow), + ): + # Start both flows + await hass.config_entries.flow.async_init( + "discovered_integration", + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + await hass.config_entries.flow.async_init( + "user_flow_integration", + context={"source": config_entries.SOURCE_USER}, + ) + + # Verify both flows are in progress + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 2 + + websocket_client = await hass_ws_client() + await websocket_client.send_json({"id": 7, "type": "logger/log_info"}) + + msg = await websocket_client.receive_json() + assert msg["id"] == 7 + assert msg["type"] == TYPE_RESULT + assert msg["success"] + + domains = [item["domain"] for item in msg["result"]] + # Discovery flow should be included + assert "discovered_integration" in domains + # User flow should NOT be included (not a discovery source) + assert "user_flow_integration" not in domains + + +async def test_integration_log_info_with_settings( + hass: HomeAssistant, hass_ws_client: WebSocketGenerator, hass_admin_user: MockUser +) -> None: + """Test that log info includes integrations with custom log settings.""" + assert await async_setup_component(hass, "logger", {}) + + # Set up a mock integration that is not loaded + mock_integration(hass, MockModule("unloaded_integration")) + + # Set a log level for this unloaded integration + websocket_client = await hass_ws_client() + await websocket_client.send_json( + { + "id": 1, + "type": "logger/integration_log_level", + "integration": "unloaded_integration", + "level": "DEBUG", + "persistence": "none", + } + ) + msg = await websocket_client.receive_json() + assert msg["success"] + + # Now check log_info includes the unloaded integration with settings + await websocket_client.send_json({"id": 2, "type": "logger/log_info"}) + + msg = await websocket_client.receive_json() + assert msg["id"] == 2 + assert msg["success"] + + domains = [item["domain"] for item in msg["result"]] + assert "unloaded_integration" in domains + + async def test_integration_log_level_logger_not_loaded( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, hass_admin_user: MockUser ) -> None: