From 52a4c169804c5718917420f163ca06c57e7a3302 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 18 Mar 2020 00:29:12 +0100 Subject: [PATCH] Improve MQTT light test coverage (#32907) --- .../components/mqtt/light/schema_json.py | 4 +- .../components/mqtt/light/schema_template.py | 3 + tests/components/mqtt/test_light.py | 173 ++++++++- tests/components/mqtt/test_light_json.py | 115 +++++- tests/components/mqtt/test_light_template.py | 331 +++++++++++++++++- 5 files changed, 613 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 60ecf80fb63..0af5aaf2c76 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -284,7 +284,7 @@ class MqttLightJson( ) except KeyError: pass - except ValueError: + except (TypeError, ValueError): _LOGGER.warning("Invalid brightness value received") if self._color_temp is not None: @@ -300,8 +300,6 @@ class MqttLightJson( self._effect = values["effect"] except KeyError: pass - except ValueError: - _LOGGER.warning("Invalid effect value received") if self._white_value is not None: try: diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 853e7f4411f..cd3e704f624 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -434,6 +434,9 @@ class MqttTemplate( if ATTR_EFFECT in kwargs: values["effect"] = kwargs.get(ATTR_EFFECT) + if self._optimistic: + self._effect = kwargs[ATTR_EFFECT] + if ATTR_FLASH in kwargs: values["flash"] = kwargs.get(ATTR_FLASH) diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index 895995e06d9..ba4078f5374 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -335,6 +335,105 @@ async def test_controlling_state_via_topic(hass, mqtt_mock): assert light_state.attributes.get("xy_color") == (0.672, 0.324) +async def test_invalid_state_via_topic(hass, mqtt_mock, caplog): + """Test handling of empty data via topic.""" + config = { + light.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "test_light_rgb/status", + "command_topic": "test_light_rgb/set", + "brightness_state_topic": "test_light_rgb/brightness/status", + "brightness_command_topic": "test_light_rgb/brightness/set", + "rgb_state_topic": "test_light_rgb/rgb/status", + "rgb_command_topic": "test_light_rgb/rgb/set", + "color_temp_state_topic": "test_light_rgb/color_temp/status", + "color_temp_command_topic": "test_light_rgb/color_temp/set", + "effect_state_topic": "test_light_rgb/effect/status", + "effect_command_topic": "test_light_rgb/effect/set", + "hs_state_topic": "test_light_rgb/hs/status", + "hs_command_topic": "test_light_rgb/hs/set", + "white_value_state_topic": "test_light_rgb/white_value/status", + "white_value_command_topic": "test_light_rgb/white_value/set", + "xy_state_topic": "test_light_rgb/xy/status", + "xy_command_topic": "test_light_rgb/xy/set", + "qos": "0", + "payload_on": 1, + "payload_off": 0, + } + } + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + assert state.attributes.get("rgb_color") is None + assert state.attributes.get("brightness") is None + assert state.attributes.get("color_temp") is None + assert state.attributes.get("effect") is None + assert state.attributes.get("hs_color") is None + assert state.attributes.get("white_value") is None + assert state.attributes.get("xy_color") is None + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "test_light_rgb/status", "1") + + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("rgb_color") == (255, 255, 255) + assert state.attributes.get("brightness") == 255 + assert state.attributes.get("color_temp") == 150 + assert state.attributes.get("effect") == "none" + assert state.attributes.get("hs_color") == (0, 0) + assert state.attributes.get("white_value") == 255 + assert state.attributes.get("xy_color") == (0.323, 0.329) + + async_fire_mqtt_message(hass, "test_light_rgb/status", "") + assert "Ignoring empty state message" in caplog.text + light_state = hass.states.get("light.test") + assert state.state == STATE_ON + + async_fire_mqtt_message(hass, "test_light_rgb/brightness/status", "") + assert "Ignoring empty brightness message" in caplog.text + light_state = hass.states.get("light.test") + assert light_state.attributes["brightness"] == 255 + + async_fire_mqtt_message(hass, "test_light_rgb/color_temp/status", "") + assert "Ignoring empty color temp message" in caplog.text + light_state = hass.states.get("light.test") + assert light_state.attributes["color_temp"] == 150 + + async_fire_mqtt_message(hass, "test_light_rgb/effect/status", "") + assert "Ignoring empty effect message" in caplog.text + light_state = hass.states.get("light.test") + assert light_state.attributes["effect"] == "none" + + async_fire_mqtt_message(hass, "test_light_rgb/white_value/status", "") + assert "Ignoring empty white value message" in caplog.text + light_state = hass.states.get("light.test") + assert light_state.attributes["white_value"] == 255 + + async_fire_mqtt_message(hass, "test_light_rgb/rgb/status", "") + assert "Ignoring empty rgb message" in caplog.text + light_state = hass.states.get("light.test") + assert light_state.attributes.get("rgb_color") == (255, 255, 255) + + async_fire_mqtt_message(hass, "test_light_rgb/hs/status", "") + assert "Ignoring empty hs message" in caplog.text + light_state = hass.states.get("light.test") + assert light_state.attributes.get("hs_color") == (0, 0) + + async_fire_mqtt_message(hass, "test_light_rgb/hs/status", "bad,bad") + assert "Failed to parse hs state update" in caplog.text + light_state = hass.states.get("light.test") + assert light_state.attributes.get("hs_color") == (0, 0) + + async_fire_mqtt_message(hass, "test_light_rgb/xy/status", "") + assert "Ignoring empty xy-color message" in caplog.text + light_state = hass.states.get("light.test") + assert light_state.attributes.get("xy_color") == (0.323, 0.329) + + async def test_brightness_controlling_scale(hass, mqtt_mock): """Test the brightness controlling scale.""" with assert_setup_component(1, light.DOMAIN): @@ -756,7 +855,7 @@ async def test_show_color_temp_only_if_command_topic(hass, mqtt_mock): async def test_show_effect_only_if_command_topic(hass, mqtt_mock): - """Test the color temp only if a command topic is present.""" + """Test the effect only if a command topic is present.""" config = { light.DOMAIN: { "platform": "mqtt", @@ -1013,6 +1112,78 @@ async def test_on_command_rgb(hass, mqtt_mock): mqtt_mock.async_publish.assert_called_once_with("test_light/set", "OFF", 0, False) +async def test_on_command_rgb_template(hass, mqtt_mock): + """Test on command in RGB brightness mode with RGB template.""" + config = { + light.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "test_light/set", + "rgb_command_topic": "test_light/rgb", + "rgb_command_template": "{{ red }}/{{ green }}/{{ blue }}", + } + } + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + await common.async_turn_on(hass, "light.test", brightness=127) + + # Should get the following MQTT messages. + # test_light/rgb: '127,127,127' + # test_light/set: 'ON' + mqtt_mock.async_publish.assert_has_calls( + [ + mock.call("test_light/rgb", "127/127/127", 0, False), + mock.call("test_light/set", "ON", 0, False), + ], + any_order=True, + ) + mqtt_mock.async_publish.reset_mock() + + await common.async_turn_off(hass, "light.test") + + mqtt_mock.async_publish.assert_called_once_with("test_light/set", "OFF", 0, False) + + +async def test_effect(hass, mqtt_mock): + """Test effect.""" + config = { + light.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "test_light/set", + "effect_command_topic": "test_light/effect/set", + "effect_list": ["rainbow", "colorloop"], + } + } + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + await common.async_turn_on(hass, "light.test", effect="rainbow") + + # Should get the following MQTT messages. + # test_light/effect/set: 'rainbow' + # test_light/set: 'ON' + mqtt_mock.async_publish.assert_has_calls( + [ + mock.call("test_light/effect/set", "rainbow", 0, False), + mock.call("test_light/set", "ON", 0, False), + ], + any_order=True, + ) + mqtt_mock.async_publish.reset_mock() + + await common.async_turn_off(hass, "light.test") + + mqtt_mock.async_publish.assert_called_once_with("test_light/set", "OFF", 0, False) + + async def test_availability_without_topic(hass, mqtt_mock): """Test availability without defined availability topic.""" await help_test_availability_without_topic( diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index 412c757e059..6a8bf10dc3d 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -364,6 +364,18 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): state = hass.states.get("light.test") assert state.state == STATE_ON + await common.async_turn_on(hass, "light.test", color_temp=90) + + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", + JsonValidator('{"state": "ON", "color_temp": 90}'), + 2, + False, + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + await common.async_turn_off(hass, "light.test") mqtt_mock.async_publish.assert_called_once_with( @@ -666,6 +678,64 @@ async def test_sending_xy_color(hass, mqtt_mock): ) +async def test_effect(hass, mqtt_mock): + """Test for effect being sent when included.""" + assert await async_setup_component( + hass, + light.DOMAIN, + { + light.DOMAIN: { + "platform": "mqtt", + "schema": "json", + "name": "test", + "command_topic": "test_light_rgb/set", + "effect": True, + "qos": 0, + } + }, + ) + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44 + + await common.async_turn_on(hass, "light.test") + + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", JsonValidator('{"state": "ON"}'), 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("effect") == "none" + + await common.async_turn_on(hass, "light.test", effect="rainbow") + + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", + JsonValidator('{"state": "ON", "effect": "rainbow"}'), + 0, + False, + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("effect") == "rainbow" + + await common.async_turn_on(hass, "light.test", effect="colorloop") + + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", + JsonValidator('{"state": "ON", "effect": "colorloop"}'), + 0, + False, + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("effect") == "colorloop" + + async def test_flash_short_and_long(hass, mqtt_mock): """Test for flash length being sent when included.""" assert await async_setup_component( @@ -792,8 +862,8 @@ async def test_brightness_scale(hass, mqtt_mock): assert state.attributes.get("brightness") == 255 -async def test_invalid_color_brightness_and_white_values(hass, mqtt_mock): - """Test that invalid color/brightness/white values are ignored.""" +async def test_invalid_values(hass, mqtt_mock): + """Test that invalid color/brightness/white/etc. values are ignored.""" assert await async_setup_component( hass, light.DOMAIN, @@ -805,6 +875,7 @@ async def test_invalid_color_brightness_and_white_values(hass, mqtt_mock): "state_topic": "test_light_rgb", "command_topic": "test_light_rgb/set", "brightness": True, + "color_temp": True, "rgb": True, "white_value": True, "qos": "0", @@ -814,10 +885,11 @@ async def test_invalid_color_brightness_and_white_values(hass, mqtt_mock): state = hass.states.get("light.test") assert state.state == STATE_OFF - assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 185 + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 187 assert state.attributes.get("rgb_color") is None assert state.attributes.get("brightness") is None assert state.attributes.get("white_value") is None + assert state.attributes.get("color_temp") is None assert not state.attributes.get(ATTR_ASSUMED_STATE) # Turn on the light @@ -827,7 +899,9 @@ async def test_invalid_color_brightness_and_white_values(hass, mqtt_mock): '{"state":"ON",' '"color":{"r":255,"g":255,"b":255},' '"brightness": 255,' - '"white_value": 255}', + '"white_value": 255,' + '"color_temp": 100,' + '"effect": "rainbow"}', ) state = hass.states.get("light.test") @@ -835,8 +909,19 @@ async def test_invalid_color_brightness_and_white_values(hass, mqtt_mock): assert state.attributes.get("rgb_color") == (255, 255, 255) assert state.attributes.get("brightness") == 255 assert state.attributes.get("white_value") == 255 + assert state.attributes.get("color_temp") == 100 - # Bad color values + # Bad HS color values + async_fire_mqtt_message( + hass, "test_light_rgb", '{"state":"ON",' '"color":{"h":"bad","s":"val"}}', + ) + + # Color should not have changed + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("rgb_color") == (255, 255, 255) + + # Bad RGB color values async_fire_mqtt_message( hass, "test_light_rgb", @@ -848,6 +933,16 @@ async def test_invalid_color_brightness_and_white_values(hass, mqtt_mock): assert state.state == STATE_ON assert state.attributes.get("rgb_color") == (255, 255, 255) + # Bad XY color values + async_fire_mqtt_message( + hass, "test_light_rgb", '{"state":"ON",' '"color":{"x":"bad","y":"val"}}', + ) + + # Color should not have changed + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("rgb_color") == (255, 255, 255) + # Bad brightness values async_fire_mqtt_message( hass, "test_light_rgb", '{"state":"ON",' '"brightness": "badValue"}' @@ -868,6 +963,16 @@ async def test_invalid_color_brightness_and_white_values(hass, mqtt_mock): assert state.state == STATE_ON assert state.attributes.get("white_value") == 255 + # Bad color temperature + async_fire_mqtt_message( + hass, "test_light_rgb", '{"state":"ON",' '"color_temp": "badValue"}' + ) + + # Color temperature should not have changed + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("color_temp") == 100 + async def test_availability_without_topic(hass, mqtt_mock): """Test availability without defined availability topic.""" diff --git a/tests/components/mqtt/test_light_template.py b/tests/components/mqtt/test_light_template.py index dac15e5ef53..f3965479c14 100644 --- a/tests/components/mqtt/test_light_template.py +++ b/tests/components/mqtt/test_light_template.py @@ -30,7 +30,12 @@ from unittest.mock import patch from homeassistant.components import light, mqtt from homeassistant.components.mqtt.discovery import async_start -from homeassistant.const import ATTR_ASSUMED_STATE, STATE_OFF, STATE_ON +from homeassistant.const import ( + ATTR_ASSUMED_STATE, + ATTR_SUPPORTED_FEATURES, + STATE_OFF, + STATE_ON, +) import homeassistant.core as ha from homeassistant.setup import async_setup_component @@ -60,6 +65,7 @@ from tests.common import ( async_fire_mqtt_message, mock_coro, ) +from tests.components.light import common DEFAULT_CONFIG = { light.DOMAIN: { @@ -269,8 +275,8 @@ async def test_state_brightness_color_effect_temp_white_change_via_topic( assert light_state.attributes.get("effect") == "rainbow" -async def test_optimistic(hass, mqtt_mock): - """Test optimistic mode.""" +async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock): + """Test the sending of command in optimistic mode.""" fake_state = ha.State( "light.test", "on", @@ -320,9 +326,284 @@ async def test_optimistic(hass, mqtt_mock): assert state.attributes.get("white_value") == 50 assert state.attributes.get(ATTR_ASSUMED_STATE) + await common.async_turn_off(hass, "light.test") + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "off", 2, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + await common.async_turn_on(hass, "light.test") + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,,,--", 2, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + + # Set color_temp + await common.async_turn_on(hass, "light.test", color_temp=70) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,70,,--", 2, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("color_temp") == 70 + + # Set full brightness + await common.async_turn_on(hass, "light.test", brightness=255) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,255,,,--", 2, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("brightness") == 255 + + # Full brightness - no scaling of RGB values sent over MQTT + await common.async_turn_on( + hass, "light.test", rgb_color=[255, 128, 0], white_value=80 + ) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,,80,255-128-0", 2, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("white_value") == 80 + assert state.attributes.get("rgb_color") == (255, 128, 0) + + # Full brightness - normalization of RGB values sent over MQTT + await common.async_turn_on(hass, "light.test", rgb_color=[128, 64, 0]) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,,,255-127-0", 2, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("rgb_color") == (255, 127, 0) + + # Set half brightness + await common.async_turn_on(hass, "light.test", brightness=128) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,128,,,--", 2, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("brightness") == 128 + + # Half brightness - scaling of RGB values sent over MQTT + await common.async_turn_on( + hass, "light.test", rgb_color=[0, 255, 128], white_value=40 + ) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,,40,0-128-64", 2, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("white_value") == 40 + assert state.attributes.get("rgb_color") == (0, 255, 128) + + # Half brightness - normalization+scaling of RGB values sent over MQTT + await common.async_turn_on( + hass, "light.test", rgb_color=[0, 32, 16], white_value=40 + ) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,,40,0-128-64", 2, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("white_value") == 40 + assert state.attributes.get("rgb_color") == (0, 255, 127) + + +async def test_sending_mqtt_commands_non_optimistic_brightness_template( + hass, mqtt_mock +): + """Test the sending of command in optimistic mode.""" + with assert_setup_component(1, light.DOMAIN): + assert await async_setup_component( + hass, + light.DOMAIN, + { + light.DOMAIN: { + "platform": "mqtt", + "schema": "template", + "name": "test", + "effect_list": ["rainbow", "colorloop"], + "state_topic": "test_light_rgb", + "command_topic": "test_light_rgb/set", + "command_on_template": "on," + "{{ brightness|d }}," + "{{ color_temp|d }}," + "{{ white_value|d }}," + "{{ red|d }}-" + "{{ green|d }}-" + "{{ blue|d }}", + "command_off_template": "off", + "state_template": '{{ value.split(",")[0] }}', + "brightness_template": '{{ value.split(",")[1] }}', + "color_temp_template": '{{ value.split(",")[2] }}', + "white_value_template": '{{ value.split(",")[3] }}', + "red_template": '{{ value.split(",")[4].' 'split("-")[0] }}', + "green_template": '{{ value.split(",")[4].' 'split("-")[1] }}', + "blue_template": '{{ value.split(",")[4].' 'split("-")[2] }}', + "effect_template": '{{ value.split(",")[5] }}', + } + }, + ) + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + assert not state.attributes.get("brightness") + assert not state.attributes.get("hs_color") + assert not state.attributes.get("effect") + assert not state.attributes.get("color_temp") + assert not state.attributes.get("white_value") + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + await common.async_turn_off(hass, "light.test") + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "off", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + await common.async_turn_on(hass, "light.test") + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,,,--", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + # Set color_temp + await common.async_turn_on(hass, "light.test", color_temp=70) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,70,,--", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_OFF + assert not state.attributes.get("color_temp") + + # Set full brightness + await common.async_turn_on(hass, "light.test", brightness=255) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,255,,,--", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_OFF + assert not state.attributes.get("brightness") + + # Full brightness - no scaling of RGB values sent over MQTT + await common.async_turn_on( + hass, "light.test", rgb_color=[255, 128, 0], white_value=80 + ) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,,80,255-128-0", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_OFF + assert not state.attributes.get("white_value") + assert not state.attributes.get("rgb_color") + + # Full brightness - normalization of RGB values sent over MQTT + await common.async_turn_on(hass, "light.test", rgb_color=[128, 64, 0]) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,,,255-127-0", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Set half brightness + await common.async_turn_on(hass, "light.test", brightness=128) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,128,,,--", 0, False + ) + mqtt_mock.async_publish.reset_mock() + + # Half brightness - no scaling of RGB values sent over MQTT + await common.async_turn_on( + hass, "light.test", rgb_color=[0, 255, 128], white_value=40 + ) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,,40,0-255-128", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + + # Half brightness - normalization but no scaling of RGB values sent over MQTT + await common.async_turn_on( + hass, "light.test", rgb_color=[0, 32, 16], white_value=40 + ) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,,,40,0-255-127", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + + +async def test_effect(hass, mqtt_mock): + """Test effect sent over MQTT in optimistic mode.""" + with assert_setup_component(1, light.DOMAIN): + assert await async_setup_component( + hass, + light.DOMAIN, + { + light.DOMAIN: { + "platform": "mqtt", + "schema": "template", + "effect_list": ["rainbow", "colorloop"], + "name": "test", + "command_topic": "test_light_rgb/set", + "command_on_template": "on,{{ effect }}", + "command_off_template": "off", + "qos": 0, + } + }, + ) + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44 + + await common.async_turn_on(hass, "light.test") + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert not state.attributes.get("effect") + + await common.async_turn_on(hass, "light.test", effect="rainbow") + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,rainbow", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("effect") == "rainbow" + + await common.async_turn_on(hass, "light.test", effect="colorloop") + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,colorloop", 0, False + ) + state = hass.states.get("light.test") + assert state.state == STATE_ON + assert state.attributes.get("effect") == "colorloop" + async def test_flash(hass, mqtt_mock): - """Test flash.""" + """Test flash sent over MQTT in optimistic mode.""" with assert_setup_component(1, light.DOMAIN): assert await async_setup_component( hass, @@ -342,6 +623,30 @@ async def test_flash(hass, mqtt_mock): state = hass.states.get("light.test") assert state.state == STATE_OFF + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40 + + await common.async_turn_on(hass, "light.test") + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + + await common.async_turn_on(hass, "light.test", flash="short") + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,short", 0, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + + await common.async_turn_on(hass, "light.test", flash="long") + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,long", 0, False + ) + state = hass.states.get("light.test") + assert state.state == STATE_ON async def test_transition(hass, mqtt_mock): @@ -358,6 +663,7 @@ async def test_transition(hass, mqtt_mock): "command_topic": "test_light_rgb/set", "command_on_template": "on,{{ transition }}", "command_off_template": "off,{{ transition|d }}", + "qos": 1, } }, ) @@ -365,6 +671,23 @@ async def test_transition(hass, mqtt_mock): state = hass.states.get("light.test") assert state.state == STATE_OFF + assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40 + + await common.async_turn_on(hass, "light.test", transition=10) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "on,10", 1, False + ) + mqtt_mock.async_publish.reset_mock() + state = hass.states.get("light.test") + assert state.state == STATE_ON + + await common.async_turn_off(hass, "light.test", transition=20) + mqtt_mock.async_publish.assert_called_once_with( + "test_light_rgb/set", "off,20", 1, False + ) + state = hass.states.get("light.test") + assert state.state == STATE_OFF + async def test_invalid_values(hass, mqtt_mock): """Test that invalid values are ignored."""