From 14ffda975893ebe897ef01e10458f941cec878ac Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Tue, 28 Mar 2023 09:37:07 +0200 Subject: [PATCH] Remove dependency on async_setup from mqtt integration (#87987) * Remove async_setup from mqtt integration * Final update common tests * Related tests init * Related tests diagnostics * Related tests config_flow * Cleanup and correct test * Keep websockets_api commands in async_setup --- homeassistant/components/mqtt/__init__.py | 143 +++---------- homeassistant/components/mqtt/config_flow.py | 2 +- homeassistant/components/mqtt/mixins.py | 2 +- tests/components/mqtt/test_common.py | 69 +++---- tests/components/mqtt/test_config_flow.py | 206 +++++++------------ tests/components/mqtt/test_diagnostics.py | 2 - tests/components/mqtt/test_init.py | 157 ++------------ 7 files changed, 153 insertions(+), 428 deletions(-) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 5a9eb7c3fcb..24dc4b67cd9 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -10,7 +10,7 @@ from typing import Any, cast import jinja2 import voluptuous as vol -from homeassistant import config as conf_util, config_entries +from homeassistant import config as conf_util from homeassistant.components import websocket_api from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -25,16 +25,10 @@ from homeassistant.const import ( ) from homeassistant.core import HassJob, HomeAssistant, ServiceCall, callback from homeassistant.exceptions import TemplateError, Unauthorized -from homeassistant.helpers import ( - config_validation as cv, - discovery_flow, - event, - template, -) +from homeassistant.helpers import config_validation as cv, event, template from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import async_get_platforms -from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.reload import ( async_integration_yaml_config, async_reload_integration_platforms, @@ -52,11 +46,9 @@ from .client import ( # noqa: F401 subscribe, ) from .config_integration import ( - CONFIG_SCHEMA_BASE, CONFIG_SCHEMA_ENTRY, DEFAULT_VALUES, - DEPRECATED_CERTIFICATE_CONFIG_KEYS, - DEPRECATED_CONFIG_KEYS, + PLATFORM_CONFIG_SCHEMA_BASE, ) from .const import ( # noqa: F401 ATTR_PAYLOAD, @@ -99,7 +91,6 @@ from .models import ( # noqa: F401 from .util import ( async_create_certificate_temp_files, get_mqtt_data, - migrate_certificate_file_to_content, mqtt_config_entry_enabled, valid_publish_topic, valid_qos_schema, @@ -146,22 +137,22 @@ CONFIG_ENTRY_CONFIG_KEYS = [ CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( - cv.deprecated(CONF_BIRTH_MESSAGE), # Deprecated in HA Core 2022.3 - cv.deprecated(CONF_BROKER), # Deprecated in HA Core 2022.3 - cv.deprecated(CONF_CERTIFICATE), # Deprecated in HA Core 2022.11 - cv.deprecated(CONF_CLIENT_ID), # Deprecated in HA Core 2022.11 - cv.deprecated(CONF_CLIENT_CERT), # Deprecated in HA Core 2022.11 - cv.deprecated(CONF_CLIENT_KEY), # Deprecated in HA Core 2022.11 - cv.deprecated(CONF_DISCOVERY), # Deprecated in HA Core 2022.3 - cv.deprecated(CONF_DISCOVERY_PREFIX), # Deprecated in HA Core 2022.11 - cv.deprecated(CONF_KEEPALIVE), # Deprecated in HA Core 2022.11 - cv.deprecated(CONF_PASSWORD), # Deprecated in HA Core 2022.3 - cv.deprecated(CONF_PORT), # Deprecated in HA Core 2022.3 - cv.deprecated(CONF_PROTOCOL), # Deprecated in HA Core 2022.11 - cv.deprecated(CONF_TLS_INSECURE), # Deprecated in HA Core 2022.11 - cv.deprecated(CONF_USERNAME), # Deprecated in HA Core 2022.3 - cv.deprecated(CONF_WILL_MESSAGE), # Deprecated in HA Core 2022.3 - CONFIG_SCHEMA_BASE, + cv.removed(CONF_BIRTH_MESSAGE), # Removed in HA Core 2023.4 + cv.removed(CONF_BROKER), # Removed in HA Core 2023.4 + cv.removed(CONF_CERTIFICATE), # Removed in HA Core 2023.4 + cv.removed(CONF_CLIENT_ID), # Removed in HA Core 2023.4 + cv.removed(CONF_CLIENT_CERT), # Removed in HA Core 2023.4 + cv.removed(CONF_CLIENT_KEY), # Removed in HA Core 2023.4 + cv.removed(CONF_DISCOVERY), # Removed in HA Core 2022.3 + cv.removed(CONF_DISCOVERY_PREFIX), # Removed in HA Core 2023.4 + cv.removed(CONF_KEEPALIVE), # Removed in HA Core 2023.4 + cv.removed(CONF_PASSWORD), # Removed in HA Core 2023.4 + cv.removed(CONF_PORT), # Removed in HA Core 2023.4 + cv.removed(CONF_PROTOCOL), # Removed in HA Core 2023.4 + cv.removed(CONF_TLS_INSECURE), # Removed in HA Core 2023.4 + cv.removed(CONF_USERNAME), # Removed in HA Core 2023.4 + cv.removed(CONF_WILL_MESSAGE), # Removed in HA Core 2023.4 + PLATFORM_CONFIG_SCHEMA_BASE, ) }, extra=vol.ALLOW_EXTRA, @@ -197,34 +188,8 @@ async def _async_setup_discovery( async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the MQTT protocol service.""" - mqtt_data = get_mqtt_data(hass, True) - - conf: ConfigType | None = config.get(DOMAIN) - websocket_api.async_register_command(hass, websocket_subscribe) websocket_api.async_register_command(hass, websocket_mqtt_info) - - if conf: - conf = dict(conf) - mqtt_data.config = conf - - if (mqtt_entry_status := mqtt_config_entry_enabled(hass)) is None: - # Create an import flow if the user has yaml configured entities etc. - # but no broker configuration. Note: The intention is not for this to - # import broker configuration from YAML because that has been deprecated. - discovery_flow.async_create_flow( - hass, - DOMAIN, - context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, - data={}, - ) - mqtt_data.reload_needed = True - elif mqtt_entry_status is False: - _LOGGER.info( - "MQTT will be not available until the config entry is enabled", - ) - mqtt_data.reload_needed = True - return True @@ -247,30 +212,15 @@ def _filter_entry_config(hass: HomeAssistant, entry: ConfigEntry) -> None: hass.config_entries.async_update_entry(entry, data=filtered_data) -async def _async_merge_basic_config( +async def _async_auto_mend_config( hass: HomeAssistant, entry: ConfigEntry, yaml_config: dict[str, Any] ) -> None: - """Merge basic options in configuration.yaml config with config entry. + """Mends config fetched from config entry and adds missing values. This mends incomplete migration from old version of HA Core. """ entry_updated = False entry_config = {**entry.data} - for key in DEPRECATED_CERTIFICATE_CONFIG_KEYS: - if key in yaml_config and key not in entry_config: - if ( - content := await hass.async_add_executor_job( - migrate_certificate_file_to_content, yaml_config[key] - ) - ) is not None: - entry_config[key] = content - entry_updated = True - - for key in DEPRECATED_CONFIG_KEYS: - if key in yaml_config and key not in entry_config: - entry_config[key] = yaml_config[key] - entry_updated = True - for key in MANDATORY_DEFAULT_VALUES: if key not in entry_config: entry_config[key] = DEFAULT_VALUES[key] @@ -298,17 +248,16 @@ async def _async_config_entry_updated(hass: HomeAssistant, entry: ConfigEntry) - async def async_fetch_config( hass: HomeAssistant, entry: ConfigEntry ) -> dict[str, Any] | None: - """Fetch fresh MQTT yaml config from the hass config when (re)loading the entry.""" + """Fetch fresh MQTT yaml config from the hass config.""" mqtt_data = get_mqtt_data(hass) - if mqtt_data.reload_entry: - hass_config = await conf_util.async_hass_config_yaml(hass) - mqtt_data.config = CONFIG_SCHEMA_BASE(hass_config.get(DOMAIN, {})) + hass_config = await conf_util.async_hass_config_yaml(hass) + mqtt_data.config = PLATFORM_CONFIG_SCHEMA_BASE(hass_config.get(DOMAIN, {})) # Remove unknown keys from config entry data _filter_entry_config(hass, entry) - # Merge basic configuration, and add missing defaults for basic options - await _async_merge_basic_config(hass, entry, mqtt_data.config or {}) + # Add missing defaults to migrate older config entries + await _async_auto_mend_config(hass, entry, mqtt_data.config or {}) # Bail out if broker setting is missing if CONF_BROKER not in entry.data: _LOGGER.error("MQTT broker is not configured, please configure it") @@ -319,37 +268,6 @@ async def async_fetch_config( if (conf := mqtt_data.config) is None: conf = CONFIG_SCHEMA_ENTRY(dict(entry.data)) - # User has configuration.yaml config, warn about config entry overrides - elif any(key in conf for key in entry.data): - shared_keys = conf.keys() & entry.data.keys() - override = {k: entry.data[k] for k in shared_keys if conf[k] != entry.data[k]} - if CONF_PASSWORD in override: - override[CONF_PASSWORD] = "********" - if CONF_CLIENT_KEY in override: - override[CONF_CLIENT_KEY] = "-----PRIVATE KEY-----" - if override: - _LOGGER.warning( - ( - "Deprecated configuration settings found in configuration.yaml. " - "These settings from your configuration entry will override: %s" - ), - override, - ) - # Register a repair issue - async_create_issue( - hass, - DOMAIN, - "deprecated_yaml_broker_settings", - breaks_in_ha_version="2023.4.0", # Warning first added in 2022.11.0 - is_fixable=False, - severity=IssueSeverity.WARNING, - translation_key="deprecated_yaml_broker_settings", - translation_placeholders={ - "more_info_url": "https://www.home-assistant.io/integrations/mqtt/", - "deprecated_settings": str(shared_keys)[1:-1], - }, - ) - # Merge advanced configuration values from configuration.yaml conf = _merge_extended_config(entry, conf) return conf @@ -359,10 +277,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Load a config entry.""" mqtt_data = get_mqtt_data(hass, True) - # Merge basic configuration, and add missing defaults for basic options + # Fetch configuration and add missing defaults for basic options if (conf := await async_fetch_config(hass, entry)) is None: # Bail out return False + await async_create_certificate_temp_files(hass, dict(entry.data)) mqtt_data.client = MQTT(hass, entry, conf) # Restore saved subscriptions @@ -480,6 +399,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def _reload_config(call: ServiceCall) -> None: """Reload the platforms.""" + # Fetch updated manual configured items and validate + config_yaml = await async_integration_yaml_config(hass, DOMAIN) or {} + mqtt_data.updated_config = config_yaml.get(DOMAIN, {}) + # Reload the modern yaml platforms mqtt_platforms = async_get_platforms(hass, DOMAIN) tasks = [ @@ -493,8 +416,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ] await asyncio.gather(*tasks) - config_yaml = await async_integration_yaml_config(hass, DOMAIN) or {} - mqtt_data.updated_config = config_yaml.get(DOMAIN, {}) await asyncio.gather( *( [ diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 66424f2c3dc..77c3856aac1 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -588,7 +588,7 @@ async def async_get_broker_settings( current_user = user_input_basic.get(CONF_USERNAME) current_pass = user_input_basic.get(CONF_PASSWORD) else: - # Get default settings from entry or yaml (if any) + # Get default settings from entry (if any) current_broker = current_config.get(CONF_BROKER) current_port = current_config.get(CONF_PORT, DEFAULT_PORT) current_user = current_config.get(CONF_USERNAME) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index b52c57ce24f..cecb4b88bcd 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -247,7 +247,7 @@ def warn_for_legacy_schema(domain: str) -> Callable[[ConfigType], ConfigType]: ( "Manually configured MQTT %s(s) found under platform key '%s', " "please move to the mqtt integration key, see " - "https://www.home-assistant.io/integrations/%s.mqtt/#new_format" + "https://www.home-assistant.io/integrations/%s.mqtt/" ), domain, domain, diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index 6d238a63f43..154f91974a1 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -36,7 +36,6 @@ from homeassistant.helpers import ( ) from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry, async_fire_mqtt_message from tests.typing import MqttMockHAClient, MqttMockHAClientGenerator, MqttMockPahoClient @@ -109,14 +108,15 @@ async def help_setup_component( item += 1 topic = f"homeassistant/{domain}/item_{item}/config" async_fire_mqtt_message(hass, topic, json.dumps(comp)) + await hass.async_block_till_done() else: - await async_setup_component( - hass, - mqtt.DOMAIN, - config, + entry = MockConfigEntry( + domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"} ) + entry.add_to_hass(hass) + with patch("homeassistant.config.load_yaml_config_file", return_value=config): + await entry.async_setup(hass) mqtt_mock = None - await hass.async_block_till_done() return mqtt_mock @@ -226,7 +226,7 @@ async def help_test_default_availability_payload( async def help_test_default_availability_list_payload( hass: HomeAssistant, - mqtt_mock_entry_with_no_config: MqttMockHAClientGenerator, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, domain: str, config: ConfigType, no_assumed_state: bool = False, @@ -243,7 +243,7 @@ async def help_test_default_availability_list_payload( {"topic": "availability-topic1"}, {"topic": "availability-topic2"}, ] - await help_setup_component(hass, mqtt_mock_entry_with_no_config, domain, config) + await help_setup_component(hass, mqtt_mock_entry_no_yaml_config, domain, config) state = hass.states.get(f"{domain}.test") assert state and state.state == STATE_UNAVAILABLE @@ -1169,7 +1169,7 @@ async def help_test_entity_id_update_subscriptions( entity_registry = er.async_get(hass) mqtt_mock = await help_setup_component( - hass, mqtt_mock_entry_no_yaml_config, domain, config, True + hass, mqtt_mock_entry_no_yaml_config, domain, config, use_discovery=True ) assert mqtt_mock is not None @@ -1796,27 +1796,6 @@ async def help_test_reload_with_config( await hass.async_block_till_done() -async def help_test_entry_reload_with_new_config( - hass: HomeAssistant, tmp_path: Path, new_config: ConfigType -) -> None: - """Test reloading with supplied config.""" - mqtt_config_entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] - assert mqtt_config_entry.state is ConfigEntryState.LOADED - new_yaml_config_file = tmp_path / "configuration.yaml" - new_yaml_config = yaml.dump(new_config) - new_yaml_config_file.write_text(new_yaml_config) - assert new_yaml_config_file.read_text() == new_yaml_config - - with patch.object( - module_hass_config, "YAML_CONFIG_FILE", new_yaml_config_file - ), patch("paho.mqtt.client.Client") as mock_client: - mock_client().connect = lambda *args: 0 - # reload the config entry - assert await hass.config_entries.async_reload(mqtt_config_entry.entry_id) - assert mqtt_config_entry.state is ConfigEntryState.LOADED - await hass.async_block_till_done() - - async def help_test_reloadable( hass: HomeAssistant, mqtt_client_mock: MqttMockPahoClient, @@ -1839,10 +1818,8 @@ async def help_test_reloadable( entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) entry.add_to_hass(hass) mqtt_client_mock.connect.return_value = 0 - # We should call await mqtt.async_setup_entry(hass, entry) when async_setup - # is removed (this is planned with #87987). Until then we set up the mqtt component - # to test reload after the async_setup setup has set the initial config - await help_setup_component(hass, None, domain, old_config, use_discovery=False) + with patch("homeassistant.config.load_yaml_config_file", return_value=old_config): + await entry.async_setup(hass) assert hass.states.get(f"{domain}.test_old_1") assert hass.states.get(f"{domain}.test_old_2") @@ -1860,15 +1837,15 @@ async def help_test_reloadable( new_config = { mqtt.DOMAIN: {domain: [new_config_1, new_config_2, new_config_extra]}, } - module_hass_config.load_yaml_config_file.return_value = new_config - # Reload the mqtt entry with the new config - await hass.services.async_call( - "mqtt", - SERVICE_RELOAD, - {}, - blocking=True, - ) - await hass.async_block_till_done() + with patch("homeassistant.config.load_yaml_config_file", return_value=new_config): + # Reload the mqtt entry with the new config + await hass.services.async_call( + "mqtt", + SERVICE_RELOAD, + {}, + blocking=True, + ) + await hass.async_block_till_done() assert len(hass.states.async_all(domain)) == 3 @@ -1900,9 +1877,9 @@ async def help_test_unload_config_entry_with_platform( config_setup: dict[str, dict[str, Any]] = copy.deepcopy(config) config_setup[mqtt.DOMAIN][domain]["name"] = "config_setup" config_name = config_setup - await help_setup_component( - hass, mqtt_mock_entry_no_yaml_config, domain, config_setup - ) + + with patch("homeassistant.config.load_yaml_config_file", return_value=config_name): + await mqtt_mock_entry_no_yaml_config() # prepare setup through discovery discovery_setup = copy.deepcopy(config[mqtt.DOMAIN][domain]) diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index 99a90bac83a..ae7c4089e54 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -9,13 +9,11 @@ from uuid import uuid4 import pytest import voluptuous as vol -import yaml -from homeassistant import config as hass_config, config_entries, data_entry_flow +from homeassistant import config_entries, data_entry_flow from homeassistant.components import mqtt from homeassistant.components.hassio import HassioServiceInfo from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry from tests.typing import MqttMockHAClientGenerator, MqttMockPahoClient @@ -267,39 +265,13 @@ async def test_user_connection_fails( assert len(mock_finish_setup.mock_calls) == 0 -async def test_manual_config_starts_discovery_flow( - hass: HomeAssistant, - mock_try_connection: MqttMockPahoClient, - mock_finish_setup: MagicMock, -) -> None: - """Test manual config initiates a discovery flow.""" - # No flows in progress - assert hass.config_entries.flow.async_progress() == [] - - # MQTT config present in yaml config - assert await async_setup_component(hass, "mqtt", {"mqtt": {}}) - await hass.async_block_till_done() - assert len(mock_finish_setup.mock_calls) == 0 - - # There should now be a discovery flow - flows = hass.config_entries.flow.async_progress() - assert len(flows) == 1 - assert flows[0]["context"]["source"] == "integration_discovery" - assert flows[0]["handler"] == "mqtt" - assert flows[0]["step_id"] == "broker" - - +@pytest.mark.parametrize("hass_config", [{"mqtt": {"sensor": {"state_topic": "test"}}}]) async def test_manual_config_set( hass: HomeAssistant, mock_try_connection: MqttMockPahoClient, mock_finish_setup: MagicMock, ) -> None: """Test manual config does not create an entry, and entry can be setup late.""" - # MQTT config present in yaml config - assert await async_setup_component(hass, "mqtt", {"mqtt": {"broker": "bla"}}) - await hass.async_block_till_done() - # do not try to reload - hass.data["mqtt"].reload_needed = False assert len(mock_finish_setup.mock_calls) == 0 mock_try_connection.return_value = True @@ -1162,6 +1134,9 @@ async def test_options_bad_will_message_fails( } +@pytest.mark.parametrize( + "hass_config", [{"mqtt": {"sensor": [{"state_topic": "some-topic"}]}}] +) async def test_try_connection_with_advanced_parameters( hass: HomeAssistant, mock_try_connection_success: MqttMockPahoClient, @@ -1170,23 +1145,6 @@ async def test_try_connection_with_advanced_parameters( mock_process_uploaded_file: MagicMock, ) -> None: """Test config flow with advanced parameters from config.""" - - with open(tmp_path / "client.crt", "wb") as certfile: - certfile.write(MOCK_CLIENT_CERT) - with open(tmp_path / "client.key", "wb") as keyfile: - keyfile.write(MOCK_CLIENT_KEY) - - config = { - "certificate": "auto", - "tls_insecure": True, - "client_cert": str(tmp_path / "client.crt"), - "client_key": str(tmp_path / "client.key"), - } - new_yaml_config_file = tmp_path / "configuration.yaml" - new_yaml_config = yaml.dump({mqtt.DOMAIN: config}) - new_yaml_config_file.write_text(new_yaml_config) - assert new_yaml_config_file.read_text() == new_yaml_config - config_entry = MockConfigEntry(domain=mqtt.DOMAIN) config_entry.add_to_hass(hass) config_entry.data = { @@ -1195,6 +1153,10 @@ async def test_try_connection_with_advanced_parameters( mqtt.CONF_USERNAME: "user", mqtt.CONF_PASSWORD: "pass", mqtt.CONF_TRANSPORT: "websockets", + mqtt.CONF_CERTIFICATE: "auto", + mqtt.CONF_TLS_INSECURE: True, + mqtt.CONF_CLIENT_CERT: MOCK_CLIENT_CERT.decode(encoding="utf-8)"), + mqtt.CONF_CLIENT_KEY: MOCK_CLIENT_KEY.decode(encoding="utf-8"), mqtt.CONF_WS_PATH: "/path/", mqtt.CONF_WS_HEADERS: {"h1": "v1", "h2": "v2"}, mqtt.CONF_KEEPALIVE: 30, @@ -1212,95 +1174,81 @@ async def test_try_connection_with_advanced_parameters( mqtt.ATTR_RETAIN: False, }, } + # Test default/suggested values from config + result = await hass.config_entries.options.async_init(config_entry.entry_id) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["step_id"] == "broker" + defaults = { + mqtt.CONF_BROKER: "test-broker", + mqtt.CONF_PORT: 1234, + "set_client_cert": True, + "set_ca_cert": "auto", + } + suggested = { + mqtt.CONF_USERNAME: "user", + mqtt.CONF_PASSWORD: "pass", + mqtt.CONF_TLS_INSECURE: True, + mqtt.CONF_PROTOCOL: "3.1.1", + mqtt.CONF_TRANSPORT: "websockets", + mqtt.CONF_WS_PATH: "/path/", + mqtt.CONF_WS_HEADERS: '{"h1":"v1","h2":"v2"}', + } + for k, v in defaults.items(): + assert get_default(result["data_schema"].schema, k) == v + for k, v in suggested.items(): + assert get_suggested(result["data_schema"].schema, k) == v - with patch.object(hass_config, "YAML_CONFIG_FILE", new_yaml_config_file): - await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: config}) - await hass.async_block_till_done() - # Test default/suggested values from config - result = await hass.config_entries.options.async_init(config_entry.entry_id) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["step_id"] == "broker" - defaults = { - mqtt.CONF_BROKER: "test-broker", - mqtt.CONF_PORT: 1234, - "set_client_cert": True, + # test we can change username and password + # as it was configured as auto in configuration.yaml is is migrated now + mock_try_connection_success.reset_mock() + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + mqtt.CONF_BROKER: "another-broker", + mqtt.CONF_PORT: 2345, + mqtt.CONF_USERNAME: "us3r", + mqtt.CONF_PASSWORD: "p4ss", "set_ca_cert": "auto", - } - suggested = { - mqtt.CONF_USERNAME: "user", - mqtt.CONF_PASSWORD: "pass", + "set_client_cert": True, mqtt.CONF_TLS_INSECURE: True, - mqtt.CONF_PROTOCOL: "3.1.1", mqtt.CONF_TRANSPORT: "websockets", - mqtt.CONF_WS_PATH: "/path/", - mqtt.CONF_WS_HEADERS: '{"h1":"v1","h2":"v2"}', - } - for k, v in defaults.items(): - assert get_default(result["data_schema"].schema, k) == v - for k, v in suggested.items(): - assert get_suggested(result["data_schema"].schema, k) == v + mqtt.CONF_WS_PATH: "/new/path", + mqtt.CONF_WS_HEADERS: '{"h3": "v3"}', + }, + ) + assert result["type"] == data_entry_flow.FlowResultType.FORM + assert result["errors"] == {} + assert result["step_id"] == "options" + await hass.async_block_till_done() - # test the client cert and key were migrated to the entry - assert config_entry.data[mqtt.CONF_CLIENT_CERT] == MOCK_CLIENT_CERT.decode( - "utf-8" - ) - assert config_entry.data[mqtt.CONF_CLIENT_KEY] == MOCK_CLIENT_KEY.decode( - "utf-8" - ) - assert config_entry.data[mqtt.CONF_CERTIFICATE] == "auto" + # check if the username and password was set from config flow and not from configuration.yaml + assert mock_try_connection_success.username_pw_set.mock_calls[0][1] == ( + "us3r", + "p4ss", + ) + # check if tls_insecure_set is called + assert mock_try_connection_success.tls_insecure_set.mock_calls[0][1] == (True,) - # test we can change username and password - # as it was configured as auto in configuration.yaml is is migrated now - mock_try_connection_success.reset_mock() - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={ - mqtt.CONF_BROKER: "another-broker", - mqtt.CONF_PORT: 2345, - mqtt.CONF_USERNAME: "us3r", - mqtt.CONF_PASSWORD: "p4ss", - "set_ca_cert": "auto", - "set_client_cert": True, - mqtt.CONF_TLS_INSECURE: True, - mqtt.CONF_TRANSPORT: "websockets", - mqtt.CONF_WS_PATH: "/new/path", - mqtt.CONF_WS_HEADERS: '{"h3": "v3"}', - }, - ) - assert result["type"] == data_entry_flow.FlowResultType.FORM - assert result["errors"] == {} - assert result["step_id"] == "options" - await hass.async_block_till_done() + # check if the ca certificate settings were not set during connection test + assert mock_try_connection_success.tls_set.mock_calls[0].kwargs[ + "certfile" + ] == mqtt.util.get_file_path(mqtt.CONF_CLIENT_CERT) + assert mock_try_connection_success.tls_set.mock_calls[0].kwargs[ + "keyfile" + ] == mqtt.util.get_file_path(mqtt.CONF_CLIENT_KEY) - # check if the username and password was set from config flow and not from configuration.yaml - assert mock_try_connection_success.username_pw_set.mock_calls[0][1] == ( - "us3r", - "p4ss", - ) - # check if tls_insecure_set is called - assert mock_try_connection_success.tls_insecure_set.mock_calls[0][1] == (True,) - - # check if the ca certificate settings were not set during connection test - assert mock_try_connection_success.tls_set.mock_calls[0].kwargs[ - "certfile" - ] == mqtt.util.get_file_path(mqtt.CONF_CLIENT_CERT) - assert mock_try_connection_success.tls_set.mock_calls[0].kwargs[ - "keyfile" - ] == mqtt.util.get_file_path(mqtt.CONF_CLIENT_KEY) - - # check if websockets options are set - assert mock_try_connection_success.ws_set_options.mock_calls[0][1] == ( - "/new/path", - {"h3": "v3"}, - ) - - # Accept default option - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={}, - ) - assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY - await hass.async_block_till_done() + # check if websockets options are set + assert mock_try_connection_success.ws_set_options.mock_calls[0][1] == ( + "/new/path", + {"h3": "v3"}, + ) + # Accept default option + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={}, + ) + assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY + await hass.async_block_till_done() async def test_setup_with_advanced_settings( diff --git a/tests/components/mqtt/test_diagnostics.py b/tests/components/mqtt/test_diagnostics.py index 780be729259..cfc6f069b02 100644 --- a/tests/components/mqtt/test_diagnostics.py +++ b/tests/components/mqtt/test_diagnostics.py @@ -25,8 +25,6 @@ default_config = { "port": 1883, "protocol": "3.1.1", "transport": "tcp", - "ws_headers": {}, - "ws_path": "/", "will_message": { "payload": "offline", "qos": 0, diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 9fbca57e3a9..cdc31429e2f 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -5,18 +5,15 @@ import copy from datetime import datetime, timedelta from functools import partial import json -from pathlib import Path import ssl from typing import Any, TypedDict from unittest.mock import ANY, MagicMock, call, mock_open, patch import pytest import voluptuous as vol -import yaml -from homeassistant import config as module_hass_config from homeassistant.components import mqtt -from homeassistant.components.mqtt import CONFIG_SCHEMA, debug_info +from homeassistant.components.mqtt import debug_info from homeassistant.components.mqtt.client import EnsureJobAfterCooldown from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA from homeassistant.components.mqtt.models import MessageCallbackType, ReceiveMessage @@ -40,10 +37,7 @@ from homeassistant.helpers.typing import ConfigType from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow -from .test_common import ( - help_test_entry_reload_with_new_config, - help_test_validate_platform_config, -) +from .test_common import help_test_validate_platform_config from tests.common import ( MockConfigEntry, @@ -1529,18 +1523,17 @@ async def test_subscribed_at_highest_qos( async def test_reload_entry_with_restored_subscriptions( hass: HomeAssistant, - tmp_path: Path, mqtt_client_mock: MqttMockPahoClient, record_calls: MessageCallbackType, calls: list[ReceiveMessage], ) -> None: """Test reloading the config entry with with subscriptions restored.""" - + # Setup the MQTT entry entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) entry.add_to_hass(hass) mqtt_client_mock.connect.return_value = 0 - assert await mqtt.async_setup_entry(hass, entry) - await hass.async_block_till_done() + with patch("homeassistant.config.load_yaml_config_file", return_value={}): + await entry.async_setup(hass) await mqtt.async_subscribe(hass, "test-topic", record_calls) await mqtt.async_subscribe(hass, "wild/+/card", record_calls) @@ -1557,10 +1550,10 @@ async def test_reload_entry_with_restored_subscriptions( calls.clear() # Reload the entry - config_yaml_new = {} - await help_test_entry_reload_with_new_config(hass, tmp_path, config_yaml_new) - - await hass.async_block_till_done() + with patch("homeassistant.config.load_yaml_config_file", return_value={}): + assert await hass.config_entries.async_reload(entry.entry_id) + assert entry.state is ConfigEntryState.LOADED + await hass.async_block_till_done() async_fire_mqtt_message(hass, "test-topic", "test-payload2") async_fire_mqtt_message(hass, "wild/any/card", "wild-card-payload2") @@ -1574,10 +1567,10 @@ async def test_reload_entry_with_restored_subscriptions( calls.clear() # Reload the entry again - config_yaml_new = {} - await help_test_entry_reload_with_new_config(hass, tmp_path, config_yaml_new) - - await hass.async_block_till_done() + with patch("homeassistant.config.load_yaml_config_file", return_value={}): + assert await hass.config_entries.async_reload(entry.entry_id) + assert entry.state is ConfigEntryState.LOADED + await hass.async_block_till_done() async_fire_mqtt_message(hass, "test-topic", "test-payload3") async_fire_mqtt_message(hass, "wild/any/card", "wild-card-payload3") @@ -1804,55 +1797,6 @@ async def test_handle_message_callback( assert callbacks[0].payload == "test-payload" -async def test_setup_override_configuration( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture, tmp_path: Path -) -> None: - """Test override setup from configuration entry.""" - calls_username_password_set = [] - - def mock_usename_password_set(username: str, password: str) -> None: - calls_username_password_set.append((username, password)) - - # Mock password setup from config - config = { - "username": "someuser", - "password": "someyamlconfiguredpassword", - "protocol": "3.1", - } - new_yaml_config_file = tmp_path / "configuration.yaml" - new_yaml_config = yaml.dump({mqtt.DOMAIN: config}) - new_yaml_config_file.write_text(new_yaml_config) - assert new_yaml_config_file.read_text() == new_yaml_config - - with patch.object(module_hass_config, "YAML_CONFIG_FILE", new_yaml_config_file): - # Mock config entry - entry = MockConfigEntry( - domain=mqtt.DOMAIN, - data={mqtt.CONF_BROKER: "test-broker", "password": "somepassword"}, - ) - entry.add_to_hass(hass) - - with patch("paho.mqtt.client.Client") as mock_client: - mock_client().username_pw_set = mock_usename_password_set - mock_client.on_connect(return_value=0) - await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: config}) - await entry.async_setup(hass) - await hass.async_block_till_done() - - assert ( - "Deprecated configuration settings found in configuration.yaml. " - "These settings from your configuration entry will override:" - in caplog.text - ) - - # Check if the protocol was set to 3.1 from configuration.yaml - assert mock_client.call_args[1]["protocol"] == 3 - - # Check if the password override worked - assert calls_username_password_set[0][0] == "someuser" - assert calls_username_password_set[0][1] == "somepassword" - - @patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_setup_manual_mqtt_with_platform_key( hass: HomeAssistant, caplog: pytest.LogCaptureFixture @@ -2312,39 +2256,10 @@ async def test_mqtt_subscribes_topics_on_connect( mqtt_client_mock.subscribe.assert_any_call("still/pending", 1) -async def test_setup_entry_with_config_override( - hass: HomeAssistant, - device_registry: dr.DeviceRegistry, - mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, -) -> None: - """Test if the MQTT component loads with no config and config entry can be setup.""" - data = ( - '{ "device":{"identifiers":["0AFFD2"]},' - ' "state_topic": "foobar/sensor",' - ' "unique_id": "unique" }' - ) - - # mqtt present in yaml config - assert await async_setup_component(hass, mqtt.DOMAIN, {}) - await hass.async_block_till_done() - - # User sets up a config entry - entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"}) - entry.add_to_hass(hass) - assert await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - - # Discover a device to verify the entry was setup correctly - async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) - await hass.async_block_till_done() - - device_entry = device_registry.async_get_device({("mqtt", "0AFFD2")}) - assert device_entry is not None - - async def test_update_incomplete_entry( hass: HomeAssistant, device_registry: dr.DeviceRegistry, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, mqtt_client_mock: MqttMockPahoClient, caplog: pytest.LogCaptureFixture, ) -> None: @@ -2356,24 +2271,17 @@ async def test_update_incomplete_entry( ) # Config entry data is incomplete - entry = MockConfigEntry(domain=mqtt.DOMAIN, data={"port": 1234}) - entry.add_to_hass(hass) - # Mqtt present in yaml config - config = {"broker": "yaml_broker"} - await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: config}) + entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0] + entry.data = {"broker": "test-broker", "port": 1234} + await mqtt_mock_entry_no_yaml_config() await hass.async_block_till_done() # Config entry data should now be updated assert dict(entry.data) == { + "broker": "test-broker", "port": 1234, "discovery_prefix": "homeassistant", - "broker": "yaml_broker", } - # Warnings about broker deprecated, but not about other keys with default values - assert ( - "The 'broker' option is deprecated, please remove it from your configuration" - in caplog.text - ) # Discover a device to verify the entry was setup correctly async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data) @@ -3219,7 +3127,7 @@ async def test_subscribe_connection_status( # This warning and test is to be removed from HA core 2023.6 async def test_one_deprecation_warning_per_platform( hass: HomeAssistant, - mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, caplog: pytest.LogCaptureFixture, ) -> None: """Test a deprecation warning is is logged once per platform.""" @@ -3230,8 +3138,6 @@ async def test_one_deprecation_warning_per_platform( config2 = copy.deepcopy(config) config2["name"] = "test2" await async_setup_component(hass, platform, {platform: [config1, config2]}) - await hass.async_block_till_done() - await mqtt_mock_entry_with_yaml_config() count = 0 for record in caplog.records: if record.levelname == "ERROR" and ( @@ -3242,13 +3148,6 @@ async def test_one_deprecation_warning_per_platform( assert count == 1 -async def test_config_schema_validation(hass: HomeAssistant) -> None: - """Test invalid platform options in the config schema do not pass the config validation.""" - config = {"mqtt": {"sensor": [{"some_illegal_topic": "mystate/topic/path"}]}} - with pytest.raises(vol.MultipleInvalid): - CONFIG_SCHEMA(config) - - @patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]) async def test_unload_config_entry( hass: HomeAssistant, @@ -3277,24 +3176,6 @@ async def test_unload_config_entry( assert "No ACK from MQTT server" not in caplog.text -@patch("homeassistant.components.mqtt.PLATFORMS", []) -async def test_setup_with_disabled_entry( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture -) -> None: - """Test setting up the platform with a disabled config entry.""" - # Try to setup the platform with a disabled config entry - config_entry = MockConfigEntry( - domain=mqtt.DOMAIN, data={}, disabled_by=ConfigEntryDisabler.USER - ) - config_entry.add_to_hass(hass) - - config: ConfigType = {mqtt.DOMAIN: {}} - await async_setup_component(hass, mqtt.DOMAIN, config) - await hass.async_block_till_done() - - assert "MQTT will be not available until the config entry is enabled" in caplog.text - - @patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_publish_or_subscribe_without_valid_config_entry( hass: HomeAssistant, record_calls: MessageCallbackType