From 57daeaa17471323806671763e012b7d5a8f53af1 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Mon, 20 Jun 2022 08:51:12 +0200 Subject: [PATCH] Fix MQTT config schema to ensure correct validation (#73619) * Ensure config schema validation * Use correct schema for device_tracker * Remove schema validation from the platform setup * Remove loop to build schema --- homeassistant/components/mqtt/__init__.py | 6 +- .../components/mqtt/alarm_control_panel.py | 2 +- .../components/mqtt/binary_sensor.py | 4 +- homeassistant/components/mqtt/button.py | 4 +- homeassistant/components/mqtt/camera.py | 4 +- homeassistant/components/mqtt/climate.py | 4 +- homeassistant/components/mqtt/config.py | 107 +--------- .../components/mqtt/config_integration.py | 193 ++++++++++++++++++ homeassistant/components/mqtt/cover.py | 2 +- .../mqtt/device_tracker/__init__.py | 1 + .../mqtt/device_tracker/schema_discovery.py | 2 +- homeassistant/components/mqtt/fan.py | 4 +- homeassistant/components/mqtt/humidifier.py | 4 +- .../components/mqtt/light/__init__.py | 2 +- homeassistant/components/mqtt/lock.py | 2 +- homeassistant/components/mqtt/mixins.py | 22 +- homeassistant/components/mqtt/number.py | 4 +- homeassistant/components/mqtt/scene.py | 2 +- homeassistant/components/mqtt/select.py | 4 +- homeassistant/components/mqtt/sensor.py | 4 +- homeassistant/components/mqtt/siren.py | 2 +- homeassistant/components/mqtt/switch.py | 4 +- .../components/mqtt/vacuum/__init__.py | 4 +- tests/components/mqtt/test_humidifier.py | 13 ++ tests/components/mqtt/test_init.py | 34 ++- 25 files changed, 258 insertions(+), 176 deletions(-) create mode 100644 homeassistant/components/mqtt/config_integration.py diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 417d1400758..82b66ddc89e 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -47,7 +47,11 @@ from .client import ( # noqa: F401 publish, subscribe, ) -from .config import CONFIG_SCHEMA_BASE, DEFAULT_VALUES, DEPRECATED_CONFIG_KEYS +from .config_integration import ( + CONFIG_SCHEMA_BASE, + DEFAULT_VALUES, + DEPRECATED_CONFIG_KEYS, +) from .const import ( # noqa: F401 ATTR_PAYLOAD, ATTR_QOS, diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index c0c6f9732d7..6bb7d9cd0d1 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -148,7 +148,7 @@ async def async_setup_entry( """Set up MQTT alarm control panel through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, alarm.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, alarm.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index cec065e20f2..39fd87c8b02 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -103,9 +103,7 @@ async def async_setup_entry( """Set up MQTT binary sensor through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, binary_sensor.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, binary_sensor.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/button.py b/homeassistant/components/mqtt/button.py index afa9900db35..370243c3579 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -83,9 +83,7 @@ async def async_setup_entry( """Set up MQTT button through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, button.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, button.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 86db828b111..5c8d3bc48b2 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -81,9 +81,7 @@ async def async_setup_entry( """Set up MQTT camera through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, camera.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, camera.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index bdcc82f2c39..a26e9cba8df 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -392,9 +392,7 @@ async def async_setup_entry( """Set up MQTT climate device through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, climate.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, climate.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/config.py b/homeassistant/components/mqtt/config.py index 4f84d911418..8cfc3490f0c 100644 --- a/homeassistant/components/mqtt/config.py +++ b/homeassistant/components/mqtt/config.py @@ -3,126 +3,21 @@ from __future__ import annotations import voluptuous as vol -from homeassistant.const import ( - CONF_CLIENT_ID, - CONF_DISCOVERY, - CONF_PASSWORD, - CONF_PORT, - CONF_PROTOCOL, - CONF_USERNAME, - CONF_VALUE_TEMPLATE, -) +from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.helpers import config_validation as cv from .const import ( - ATTR_PAYLOAD, - ATTR_QOS, - ATTR_RETAIN, - ATTR_TOPIC, - CONF_BIRTH_MESSAGE, - CONF_BROKER, - CONF_CERTIFICATE, - CONF_CLIENT_CERT, - CONF_CLIENT_KEY, CONF_COMMAND_TOPIC, - CONF_DISCOVERY_PREFIX, CONF_ENCODING, - CONF_KEEPALIVE, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, - CONF_TLS_INSECURE, - CONF_TLS_VERSION, - CONF_WILL_MESSAGE, - DEFAULT_BIRTH, - DEFAULT_DISCOVERY, DEFAULT_ENCODING, - DEFAULT_PREFIX, DEFAULT_QOS, DEFAULT_RETAIN, - DEFAULT_WILL, - PLATFORMS, - PROTOCOL_31, - PROTOCOL_311, ) from .util import _VALID_QOS_SCHEMA, valid_publish_topic, valid_subscribe_topic -DEFAULT_PORT = 1883 -DEFAULT_KEEPALIVE = 60 -DEFAULT_PROTOCOL = PROTOCOL_311 -DEFAULT_TLS_PROTOCOL = "auto" - -DEFAULT_VALUES = { - CONF_BIRTH_MESSAGE: DEFAULT_BIRTH, - CONF_DISCOVERY: DEFAULT_DISCOVERY, - CONF_PORT: DEFAULT_PORT, - CONF_TLS_VERSION: DEFAULT_TLS_PROTOCOL, - CONF_WILL_MESSAGE: DEFAULT_WILL, -} - -CLIENT_KEY_AUTH_MSG = ( - "client_key and client_cert must both be present in " - "the MQTT broker configuration" -) - -MQTT_WILL_BIRTH_SCHEMA = vol.Schema( - { - vol.Inclusive(ATTR_TOPIC, "topic_payload"): valid_publish_topic, - vol.Inclusive(ATTR_PAYLOAD, "topic_payload"): cv.string, - vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA, - vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean, - }, - required=True, -) - -PLATFORM_CONFIG_SCHEMA_BASE = vol.Schema( - {vol.Optional(platform.value): cv.ensure_list for platform in PLATFORMS} -) - -CONFIG_SCHEMA_BASE = PLATFORM_CONFIG_SCHEMA_BASE.extend( - { - vol.Optional(CONF_CLIENT_ID): cv.string, - vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All( - vol.Coerce(int), vol.Range(min=15) - ), - vol.Optional(CONF_BROKER): cv.string, - vol.Optional(CONF_PORT): cv.port, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_CERTIFICATE): vol.Any("auto", cv.isfile), - vol.Inclusive( - CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG - ): cv.isfile, - vol.Inclusive( - CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG - ): cv.isfile, - vol.Optional(CONF_TLS_INSECURE): cv.boolean, - vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"), - vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All( - cv.string, vol.In([PROTOCOL_31, PROTOCOL_311]) - ), - vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA, - vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA, - vol.Optional(CONF_DISCOVERY): cv.boolean, - # discovery_prefix must be a valid publish topic because if no - # state topic is specified, it will be created with the given prefix. - vol.Optional( - CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX - ): valid_publish_topic, - } -) - -DEPRECATED_CONFIG_KEYS = [ - CONF_BIRTH_MESSAGE, - CONF_BROKER, - CONF_DISCOVERY, - CONF_PASSWORD, - CONF_PORT, - CONF_TLS_VERSION, - CONF_USERNAME, - CONF_WILL_MESSAGE, -] - SCHEMA_BASE = { vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA, vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string, diff --git a/homeassistant/components/mqtt/config_integration.py b/homeassistant/components/mqtt/config_integration.py new file mode 100644 index 00000000000..ab685a63802 --- /dev/null +++ b/homeassistant/components/mqtt/config_integration.py @@ -0,0 +1,193 @@ +"""Support for MQTT platform config setup.""" +from __future__ import annotations + +import voluptuous as vol + +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_DISCOVERY, + CONF_PASSWORD, + CONF_PORT, + CONF_PROTOCOL, + CONF_USERNAME, + Platform, +) +from homeassistant.helpers import config_validation as cv + +from . import ( + alarm_control_panel as alarm_control_panel_platform, + binary_sensor as binary_sensor_platform, + button as button_platform, + camera as camera_platform, + climate as climate_platform, + cover as cover_platform, + device_tracker as device_tracker_platform, + fan as fan_platform, + humidifier as humidifier_platform, + light as light_platform, + lock as lock_platform, + number as number_platform, + scene as scene_platform, + select as select_platform, + sensor as sensor_platform, + siren as siren_platform, + switch as switch_platform, + vacuum as vacuum_platform, +) +from .const import ( + ATTR_PAYLOAD, + ATTR_QOS, + ATTR_RETAIN, + ATTR_TOPIC, + CONF_BIRTH_MESSAGE, + CONF_BROKER, + CONF_CERTIFICATE, + CONF_CLIENT_CERT, + CONF_CLIENT_KEY, + CONF_DISCOVERY_PREFIX, + CONF_KEEPALIVE, + CONF_TLS_INSECURE, + CONF_TLS_VERSION, + CONF_WILL_MESSAGE, + DEFAULT_BIRTH, + DEFAULT_DISCOVERY, + DEFAULT_PREFIX, + DEFAULT_QOS, + DEFAULT_RETAIN, + DEFAULT_WILL, + PROTOCOL_31, + PROTOCOL_311, +) +from .util import _VALID_QOS_SCHEMA, valid_publish_topic + +DEFAULT_PORT = 1883 +DEFAULT_KEEPALIVE = 60 +DEFAULT_PROTOCOL = PROTOCOL_311 +DEFAULT_TLS_PROTOCOL = "auto" + +DEFAULT_VALUES = { + CONF_BIRTH_MESSAGE: DEFAULT_BIRTH, + CONF_DISCOVERY: DEFAULT_DISCOVERY, + CONF_PORT: DEFAULT_PORT, + CONF_TLS_VERSION: DEFAULT_TLS_PROTOCOL, + CONF_WILL_MESSAGE: DEFAULT_WILL, +} + +PLATFORM_CONFIG_SCHEMA_BASE = vol.Schema( + { + Platform.ALARM_CONTROL_PANEL.value: vol.All( + cv.ensure_list, [alarm_control_panel_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.BINARY_SENSOR.value: vol.All( + cv.ensure_list, [binary_sensor_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.BUTTON.value: vol.All( + cv.ensure_list, [button_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.CAMERA.value: vol.All( + cv.ensure_list, [camera_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.CLIMATE.value: vol.All( + cv.ensure_list, [climate_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.COVER.value: vol.All( + cv.ensure_list, [cover_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.DEVICE_TRACKER.value: vol.All( + cv.ensure_list, [device_tracker_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.FAN.value: vol.All( + cv.ensure_list, [fan_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.HUMIDIFIER.value: vol.All( + cv.ensure_list, [humidifier_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.LOCK.value: vol.All( + cv.ensure_list, [lock_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.LIGHT.value: vol.All( + cv.ensure_list, [light_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.NUMBER.value: vol.All( + cv.ensure_list, [number_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.SCENE.value: vol.All( + cv.ensure_list, [scene_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.SELECT.value: vol.All( + cv.ensure_list, [select_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.SENSOR.value: vol.All( + cv.ensure_list, [sensor_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.SIREN.value: vol.All( + cv.ensure_list, [siren_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.SWITCH.value: vol.All( + cv.ensure_list, [switch_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + Platform.VACUUM.value: vol.All( + cv.ensure_list, [vacuum_platform.PLATFORM_SCHEMA_MODERN] # type: ignore[has-type] + ), + } +) + + +CLIENT_KEY_AUTH_MSG = ( + "client_key and client_cert must both be present in " + "the MQTT broker configuration" +) + +MQTT_WILL_BIRTH_SCHEMA = vol.Schema( + { + vol.Inclusive(ATTR_TOPIC, "topic_payload"): valid_publish_topic, + vol.Inclusive(ATTR_PAYLOAD, "topic_payload"): cv.string, + vol.Optional(ATTR_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA, + vol.Optional(ATTR_RETAIN, default=DEFAULT_RETAIN): cv.boolean, + }, + required=True, +) + +CONFIG_SCHEMA_BASE = PLATFORM_CONFIG_SCHEMA_BASE.extend( + { + vol.Optional(CONF_CLIENT_ID): cv.string, + vol.Optional(CONF_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All( + vol.Coerce(int), vol.Range(min=15) + ), + vol.Optional(CONF_BROKER): cv.string, + vol.Optional(CONF_PORT): cv.port, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_CERTIFICATE): vol.Any("auto", cv.isfile), + vol.Inclusive( + CONF_CLIENT_KEY, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG + ): cv.isfile, + vol.Inclusive( + CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG + ): cv.isfile, + vol.Optional(CONF_TLS_INSECURE): cv.boolean, + vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"), + vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All( + cv.string, vol.In([PROTOCOL_31, PROTOCOL_311]) + ), + vol.Optional(CONF_WILL_MESSAGE): MQTT_WILL_BIRTH_SCHEMA, + vol.Optional(CONF_BIRTH_MESSAGE): MQTT_WILL_BIRTH_SCHEMA, + vol.Optional(CONF_DISCOVERY): cv.boolean, + # discovery_prefix must be a valid publish topic because if no + # state topic is specified, it will be created with the given prefix. + vol.Optional( + CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX + ): valid_publish_topic, + } +) + +DEPRECATED_CONFIG_KEYS = [ + CONF_BIRTH_MESSAGE, + CONF_BROKER, + CONF_DISCOVERY, + CONF_PASSWORD, + CONF_PORT, + CONF_TLS_VERSION, + CONF_USERNAME, + CONF_WILL_MESSAGE, +] diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 8d4df0c301d..0901a4f63a6 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -241,7 +241,7 @@ async def async_setup_entry( """Set up MQTT cover through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, cover.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, cover.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/device_tracker/__init__.py b/homeassistant/components/mqtt/device_tracker/__init__.py index bcd5bbd4ee1..1b6c2b25ff3 100644 --- a/homeassistant/components/mqtt/device_tracker/__init__.py +++ b/homeassistant/components/mqtt/device_tracker/__init__.py @@ -4,6 +4,7 @@ import voluptuous as vol from homeassistant.components import device_tracker from ..mixins import warn_for_legacy_schema +from .schema_discovery import PLATFORM_SCHEMA_MODERN # noqa: F401 from .schema_discovery import async_setup_entry_from_discovery from .schema_yaml import PLATFORM_SCHEMA_YAML, async_setup_scanner_from_yaml diff --git a/homeassistant/components/mqtt/device_tracker/schema_discovery.py b/homeassistant/components/mqtt/device_tracker/schema_discovery.py index 1b48e15b80e..1ba540c8243 100644 --- a/homeassistant/components/mqtt/device_tracker/schema_discovery.py +++ b/homeassistant/components/mqtt/device_tracker/schema_discovery.py @@ -54,7 +54,7 @@ async def async_setup_entry_from_discovery(hass, config_entry, async_add_entitie *( _async_setup_entity(hass, async_add_entities, config, config_entry) for config in await async_get_platform_config_from_yaml( - hass, device_tracker.DOMAIN, PLATFORM_SCHEMA_MODERN + hass, device_tracker.DOMAIN ) ) ) diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index d0b4ff10692..4e1d1465ac2 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -231,9 +231,7 @@ async def async_setup_entry( ) -> None: """Set up MQTT fan through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml - config_entry.async_on_unload( - await async_setup_platform_discovery(hass, fan.DOMAIN, PLATFORM_SCHEMA_MODERN) - ) + config_entry.async_on_unload(await async_setup_platform_discovery(hass, fan.DOMAIN)) # setup for discovery setup = functools.partial( _async_setup_entity, hass, async_add_entities, config_entry=config_entry diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index 1c9ec5dc201..d2856767cf0 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -188,9 +188,7 @@ async def async_setup_entry( """Set up MQTT humidifier through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, humidifier.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, humidifier.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/light/__init__.py b/homeassistant/components/mqtt/light/__init__.py index 158ea6ffa0d..d4914cb9506 100644 --- a/homeassistant/components/mqtt/light/__init__.py +++ b/homeassistant/components/mqtt/light/__init__.py @@ -112,7 +112,7 @@ async def async_setup_entry( """Set up MQTT lights configured under the light platform key (deprecated).""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, light.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, light.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 0bdb66ab48b..8cf65485a09 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -104,7 +104,7 @@ async def async_setup_entry( """Set up MQTT lock through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, lock.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, lock.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index e768c2ff409..dcf387eb360 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -10,7 +10,6 @@ from typing import Any, Protocol, cast, final import voluptuous as vol -from homeassistant.config import async_log_exception from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_CONFIGURATION_URL, @@ -263,7 +262,7 @@ class SetupEntity(Protocol): async def async_setup_platform_discovery( - hass: HomeAssistant, platform_domain: str, schema: vol.Schema + hass: HomeAssistant, platform_domain: str ) -> CALLBACK_TYPE: """Set up platform discovery for manual config.""" @@ -282,7 +281,7 @@ async def async_setup_platform_discovery( *( discovery.async_load_platform(hass, platform_domain, DOMAIN, config, {}) for config in await async_get_platform_config_from_yaml( - hass, platform_domain, schema, config_yaml + hass, platform_domain, config_yaml ) ) ) @@ -295,32 +294,17 @@ async def async_setup_platform_discovery( async def async_get_platform_config_from_yaml( hass: HomeAssistant, platform_domain: str, - schema: vol.Schema, config_yaml: ConfigType = None, ) -> list[ConfigType]: """Return a list of validated configurations for the domain.""" - def async_validate_config( - hass: HomeAssistant, - config: list[ConfigType], - ) -> list[ConfigType]: - """Validate config.""" - validated_config = [] - for config_item in config: - try: - validated_config.append(schema(config_item)) - except vol.MultipleInvalid as err: - async_log_exception(err, platform_domain, config_item, hass) - - return validated_config - if config_yaml is None: config_yaml = hass.data.get(DATA_MQTT_CONFIG) if not config_yaml: return [] if not (platform_configs := config_yaml.get(platform_domain)): return [] - return async_validate_config(hass, platform_configs) + return platform_configs async def async_setup_entry_helper(hass, domain, async_setup, schema): diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index bbc78ae07db..660ffe987f0 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -136,9 +136,7 @@ async def async_setup_entry( """Set up MQTT number through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, number.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, number.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/scene.py b/homeassistant/components/mqtt/scene.py index 9c4a212bd8e..cc911cc3431 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -80,7 +80,7 @@ async def async_setup_entry( """Set up MQTT scene through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, scene.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, scene.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/select.py b/homeassistant/components/mqtt/select.py index 994c11653b7..0d9f1411fd1 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -95,9 +95,7 @@ async def async_setup_entry( """Set up MQTT select through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, select.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, select.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index f9e0b5151bb..672e22f632f 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -148,9 +148,7 @@ async def async_setup_entry( """Set up MQTT sensor through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, sensor.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, sensor.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index fef2a4fb3dd..e7b91274f4f 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -144,7 +144,7 @@ async def async_setup_entry( """Set up MQTT siren through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery(hass, siren.DOMAIN, PLATFORM_SCHEMA_MODERN) + await async_setup_platform_discovery(hass, siren.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index be7fc655e1e..dadd5f86f20 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -98,9 +98,7 @@ async def async_setup_entry( """Set up MQTT switch through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, switch.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, switch.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/homeassistant/components/mqtt/vacuum/__init__.py b/homeassistant/components/mqtt/vacuum/__init__.py index 206a15a024a..694e9530939 100644 --- a/homeassistant/components/mqtt/vacuum/__init__.py +++ b/homeassistant/components/mqtt/vacuum/__init__.py @@ -92,9 +92,7 @@ async def async_setup_entry( """Set up MQTT vacuum through configuration.yaml and dynamically through MQTT discovery.""" # load and initialize platform config from configuration.yaml config_entry.async_on_unload( - await async_setup_platform_discovery( - hass, vacuum.DOMAIN, PLATFORM_SCHEMA_MODERN - ) + await async_setup_platform_discovery(hass, vacuum.DOMAIN) ) # setup for discovery setup = functools.partial( diff --git a/tests/components/mqtt/test_humidifier.py b/tests/components/mqtt/test_humidifier.py index e1e757762df..0301e9e0481 100644 --- a/tests/components/mqtt/test_humidifier.py +++ b/tests/components/mqtt/test_humidifier.py @@ -13,6 +13,7 @@ from homeassistant.components.humidifier import ( SERVICE_SET_HUMIDITY, SERVICE_SET_MODE, ) +from homeassistant.components.mqtt import CONFIG_SCHEMA from homeassistant.components.mqtt.humidifier import ( CONF_MODE_COMMAND_TOPIC, CONF_MODE_STATE_TOPIC, @@ -1283,3 +1284,15 @@ async def test_setup_manual_entity_from_yaml(hass): del config["platform"] await help_test_setup_manual_entity_from_yaml(hass, platform, config) assert hass.states.get(f"{platform}.test") is not None + + +async def test_config_schema_validation(hass): + """Test invalid platform options in the config schema do pass the config validation.""" + platform = humidifier.DOMAIN + config = copy.deepcopy(DEFAULT_CONFIG[platform]) + config["name"] = "test" + del config["platform"] + CONFIG_SCHEMA({DOMAIN: {platform: config}}) + CONFIG_SCHEMA({DOMAIN: {platform: [config]}}) + with pytest.raises(MultipleInvalid): + CONFIG_SCHEMA({"mqtt": {"humidifier": [{"bla": "bla"}]}}) diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index b75def64834..a29f1fd88ef 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -14,7 +14,7 @@ import yaml from homeassistant import config as hass_config from homeassistant.components import mqtt -from homeassistant.components.mqtt import debug_info +from homeassistant.components.mqtt import CONFIG_SCHEMA, debug_info from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA from homeassistant.components.mqtt.models import ReceiveMessage from homeassistant.const import ( @@ -1373,40 +1373,47 @@ async def test_setup_override_configuration(hass, caplog, tmp_path): assert calls_username_password_set[0][1] == "somepassword" -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]) +@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_setup_manual_mqtt_with_platform_key(hass, caplog): """Test set up a manual MQTT item with a platform key.""" config = {"platform": "mqtt", "name": "test", "command_topic": "test-topic"} - await help_test_setup_manual_entity_from_yaml(hass, "light", config) + with pytest.raises(AssertionError): + await help_test_setup_manual_entity_from_yaml(hass, "light", config) assert ( - "Invalid config for [light]: [platform] is an invalid option for [light]. " - "Check: light->platform. (See ?, line ?)" in caplog.text + "Invalid config for [mqtt]: [platform] is an invalid option for [mqtt]" + in caplog.text ) -@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.LIGHT]) +@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_setup_manual_mqtt_with_invalid_config(hass, caplog): """Test set up a manual MQTT item with an invalid config.""" config = {"name": "test"} - await help_test_setup_manual_entity_from_yaml(hass, "light", config) + with pytest.raises(AssertionError): + await help_test_setup_manual_entity_from_yaml(hass, "light", config) assert ( - "Invalid config for [light]: required key not provided @ data['command_topic']." + "Invalid config for [mqtt]: required key not provided @ data['mqtt']['light'][0]['command_topic']." " Got None. (See ?, line ?)" in caplog.text ) +@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_setup_manual_mqtt_empty_platform(hass, caplog): """Test set up a manual MQTT platform without items.""" - config = None + config = [] await help_test_setup_manual_entity_from_yaml(hass, "light", config) assert "voluptuous.error.MultipleInvalid" not in caplog.text +@patch("homeassistant.components.mqtt.PLATFORMS", []) async def test_setup_mqtt_client_protocol(hass): """Test MQTT client protocol setup.""" entry = MockConfigEntry( domain=mqtt.DOMAIN, - data={mqtt.CONF_BROKER: "test-broker", mqtt.config.CONF_PROTOCOL: "3.1"}, + data={ + mqtt.CONF_BROKER: "test-broker", + mqtt.config_integration.CONF_PROTOCOL: "3.1", + }, ) with patch("paho.mqtt.client.Client") as mock_client: mock_client.on_connect(return_value=0) @@ -2612,3 +2619,10 @@ async def test_one_deprecation_warning_per_platform( ): count += 1 assert count == 1 + + +async def test_config_schema_validation(hass): + """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)