From 3853214496e1eff3844705d68f3c45b87d96a3d4 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Thu, 19 Oct 2023 18:48:35 +0200 Subject: [PATCH] Do not fail MQTT setup if fans configured via yaml can't be validated (#102310) Add fan --- .../components/mqtt/config_integration.py | 6 +-- homeassistant/components/mqtt/fan.py | 27 +++++-------- tests/components/mqtt/test_fan.py | 39 +++++++++++++------ 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/mqtt/config_integration.py b/homeassistant/components/mqtt/config_integration.py index cb66f0d3596..d0a5f10fc4b 100644 --- a/homeassistant/components/mqtt/config_integration.py +++ b/homeassistant/components/mqtt/config_integration.py @@ -19,7 +19,6 @@ from . import ( climate as climate_platform, cover as cover_platform, event as event_platform, - fan as fan_platform, humidifier as humidifier_platform, image as image_platform, lawn_mower as lawn_mower_platform, @@ -70,10 +69,7 @@ CONFIG_SCHEMA_BASE = vol.Schema( cv.ensure_list, [event_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.FAN.value: vol.All(cv.ensure_list, [dict]), Platform.HUMIDIFIER.value: vol.All( cv.ensure_list, [humidifier_platform.PLATFORM_SCHEMA_MODERN], # type: ignore[has-type] diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index 0aad3a6afc0..783573c8e00 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -2,7 +2,6 @@ from __future__ import annotations from collections.abc import Callable -import functools import logging import math from typing import Any @@ -30,7 +29,7 @@ from homeassistant.core import HomeAssistant, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.template import Template -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.typing import ConfigType from homeassistant.util.percentage import ( int_states_in_range, percentage_to_ranged_value, @@ -53,7 +52,7 @@ from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, - async_setup_entry_helper, + async_mqtt_entry_helper, write_state_on_attr_change, ) from .models import ( @@ -200,21 +199,15 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up MQTT fan through YAML and through MQTT discovery.""" - setup = functools.partial( - _async_setup_entity, hass, async_add_entities, config_entry=config_entry + await async_mqtt_entry_helper( + hass, + config_entry, + MqttFan, + fan.DOMAIN, + async_add_entities, + DISCOVERY_SCHEMA, + PLATFORM_SCHEMA_MODERN, ) - await async_setup_entry_helper(hass, fan.DOMAIN, setup, DISCOVERY_SCHEMA) - - -async def _async_setup_entity( - hass: HomeAssistant, - async_add_entities: AddEntitiesCallback, - config: ConfigType, - config_entry: ConfigEntry, - discovery_data: DiscoveryInfoType | None = None, -) -> None: - """Set up the MQTT fan.""" - async_add_entities([MqttFan(hass, config, config_entry, discovery_data)]) class MqttFan(MqttEntity, FanEntity): diff --git a/tests/components/mqtt/test_fan.py b/tests/components/mqtt/test_fan.py index fe354817aef..6642d778f53 100644 --- a/tests/components/mqtt/test_fan.py +++ b/tests/components/mqtt/test_fan.py @@ -96,9 +96,8 @@ async def test_fail_setup_if_no_command_topic( mqtt_mock_entry: MqttMockHAClientGenerator, ) -> None: """Test if command fails with command topic.""" - with pytest.raises(AssertionError): - await mqtt_mock_entry() - assert "Invalid config for [mqtt]: required key not provided" in caplog.text + assert await mqtt_mock_entry() + assert "required key not provided" in caplog.text @pytest.mark.parametrize( @@ -1584,7 +1583,7 @@ async def test_attributes( @pytest.mark.parametrize( - ("name", "hass_config", "success", "features"), + ("name", "hass_config", "success", "features", "error_message"), [ ( "test1", @@ -1598,6 +1597,7 @@ async def test_attributes( }, True, fan.FanEntityFeature(0), + None, ), ( "test2", @@ -1612,6 +1612,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.OSCILLATE, + None, ), ( "test3", @@ -1626,6 +1627,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.SET_SPEED, + None, ), ( "test4", @@ -1640,6 +1642,7 @@ async def test_attributes( }, False, None, + "some but not all values in the same group of inclusion 'preset_modes'", ), ( "test5", @@ -1655,6 +1658,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.PRESET_MODE, + None, ), ( "test6", @@ -1670,6 +1674,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.PRESET_MODE, + None, ), ( "test7", @@ -1684,6 +1689,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.SET_SPEED, + None, ), ( "test8", @@ -1699,6 +1705,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.OSCILLATE | fan.FanEntityFeature.SET_SPEED, + None, ), ( "test9", @@ -1714,6 +1721,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.PRESET_MODE, + None, ), ( "test10", @@ -1729,6 +1737,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.PRESET_MODE, + None, ), ( "test11", @@ -1745,6 +1754,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.PRESET_MODE | fan.FanEntityFeature.OSCILLATE, + None, ), ( "test12", @@ -1761,6 +1771,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.SET_SPEED, + None, ), ( "test13", @@ -1777,6 +1788,7 @@ async def test_attributes( }, False, None, + "not a valid value", ), ( "test14", @@ -1793,13 +1805,14 @@ async def test_attributes( }, False, None, + "not a valid value", ), ( "test15", { mqtt.DOMAIN: { fan.DOMAIN: { - "name": "test7reset_payload_in_preset_modes_a", + "name": "test15", "command_topic": "command-topic", "preset_mode_command_topic": "preset-mode-command-topic", "preset_modes": ["auto", "smart", "normal", "None"], @@ -1808,6 +1821,7 @@ async def test_attributes( }, False, None, + "preset_modes must not contain payload_reset_preset_mode", ), ( "test16", @@ -1824,6 +1838,7 @@ async def test_attributes( }, True, fan.FanEntityFeature.PRESET_MODE, + "some error", ), ( "test17", @@ -1838,25 +1853,27 @@ async def test_attributes( }, True, fan.FanEntityFeature.DIRECTION, + "some error", ), ], ) async def test_supported_features( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, name: str, success: bool, - features, + features: fan.FanEntityFeature | None, + error_message: str | None, ) -> None: """Test optimistic mode without state topic.""" + await mqtt_mock_entry() + state = hass.states.get(f"fan.{name}") + assert (state is not None) == success if success: - await mqtt_mock_entry() - - state = hass.states.get(f"fan.{name}") assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == features return - with pytest.raises(AssertionError): - await mqtt_mock_entry() + assert error_message in caplog.text @pytest.mark.parametrize("hass_config", [DEFAULT_CONFIG])