diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index c7f7fab1868..84c7a9b1078 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -1,11 +1,10 @@ """deCONZ light platform tests.""" -from copy import deepcopy +from unittest.mock import patch import pytest from homeassistant.components.deconz.const import CONF_ALLOW_DECONZ_GROUPS -from homeassistant.components.deconz.gateway import get_gateway_from_config_entry from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, @@ -36,75 +35,6 @@ from .test_gateway import ( setup_deconz_integration, ) -GROUPS = { - "1": { - "id": "Light group id", - "name": "Light group", - "type": "LightGroup", - "state": {"all_on": False, "any_on": True}, - "action": {}, - "scenes": [], - "lights": ["1", "2"], - }, - "2": { - "id": "Empty group id", - "name": "Empty group", - "type": "LightGroup", - "state": {}, - "action": {}, - "scenes": [], - "lights": [], - }, -} - -LIGHTS = { - "1": { - "id": "RGB light id", - "name": "RGB light", - "state": { - "on": True, - "bri": 255, - "colormode": "xy", - "effect": "colorloop", - "xy": (500, 500), - "reachable": True, - }, - "type": "Extended color light", - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { - "ctmax": 454, - "ctmin": 155, - "id": "Tunable white light id", - "name": "Tunable white light", - "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, - "type": "Tunable white light", - "uniqueid": "00:00:00:00:00:00:00:01-00", - }, - "3": { - "id": "On off switch id", - "name": "On off switch", - "type": "On/Off plug-in unit", - "state": {"reachable": True}, - "uniqueid": "00:00:00:00:00:00:00:02-00", - }, - "4": { - "name": "On off light", - "state": {"on": True, "reachable": True}, - "type": "On and Off light", - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "5": { - "ctmax": 1000, - "ctmin": 0, - "id": "Tunable white light with bad maxmin values id", - "name": "Tunable white light with bad maxmin values", - "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, - "type": "Tunable white light", - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, -} - async def test_no_lights_or_groups(hass, aioclient_mock): """Test that no lights or groups entities are created.""" @@ -112,15 +42,75 @@ async def test_no_lights_or_groups(hass, aioclient_mock): assert len(hass.states.async_all()) == 0 -async def test_lights_and_groups(hass, aioclient_mock): +async def test_lights_and_groups(hass, aioclient_mock, mock_deconz_websocket): """Test that lights or groups entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy(GROUPS) - data["lights"] = deepcopy(LIGHTS) - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + data = { + "groups": { + "1": { + "id": "Light group id", + "name": "Light group", + "type": "LightGroup", + "state": {"all_on": False, "any_on": True}, + "action": {}, + "scenes": [], + "lights": ["1", "2"], + }, + "2": { + "id": "Empty group id", + "name": "Empty group", + "type": "LightGroup", + "state": {}, + "action": {}, + "scenes": [], + "lights": [], + }, + }, + "lights": { + "1": { + "name": "RGB light", + "state": { + "on": True, + "bri": 255, + "colormode": "xy", + "effect": "colorloop", + "xy": (500, 500), + "reachable": True, + }, + "type": "Extended color light", + "uniqueid": "00:00:00:00:00:00:00:00-00", + }, + "2": { + "ctmax": 454, + "ctmin": 155, + "name": "Tunable white light", + "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, + "type": "Tunable white light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + "3": { + "name": "On off switch", + "type": "On/Off plug-in unit", + "state": {"reachable": True}, + "uniqueid": "00:00:00:00:00:00:00:02-00", + }, + "4": { + "name": "On off light", + "state": {"on": True, "reachable": True}, + "type": "On and Off light", + "uniqueid": "00:00:00:00:00:00:00:03-00", + }, + "5": { + "ctmax": 1000, + "ctmin": 0, + "name": "Tunable white light with bad maxmin values", + "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, + "type": "Tunable white light", + "uniqueid": "00:00:00:00:00:00:00:04-00", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 6 @@ -151,25 +141,23 @@ async def test_lights_and_groups(hass, aioclient_mock): assert on_off_light.state == STATE_ON assert on_off_light.attributes[ATTR_SUPPORTED_FEATURES] == 0 - light_group = hass.states.get("light.light_group") - assert light_group.state == STATE_ON - assert light_group.attributes["all_on"] is False + assert hass.states.get("light.light_group").state == STATE_ON + assert hass.states.get("light.light_group").attributes["all_on"] is False empty_group = hass.states.get("light.empty_group") assert empty_group is None - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": False}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() - rgb_light = hass.states.get("light.rgb_light") - assert rgb_light.state == STATE_OFF + assert hass.states.get("light.rgb_light").state == STATE_OFF # Verify service calls @@ -231,14 +219,14 @@ async def test_lights_and_groups(hass, aioclient_mock): ) assert len(aioclient_mock.mock_calls) == 3 # Not called - state_changed_event = { + event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", "state": {"on": True}, } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() # Service turn off light with short flashing @@ -272,7 +260,7 @@ async def test_lights_and_groups(hass, aioclient_mock): await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(hass.states.async_all()) == 6 + assert len(states) == 6 for state in states: assert state.state == STATE_UNAVAILABLE @@ -283,28 +271,56 @@ async def test_lights_and_groups(hass, aioclient_mock): async def test_disable_light_groups(hass, aioclient_mock): """Test disallowing light groups work.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy(GROUPS) - data["lights"] = deepcopy(LIGHTS) - config_entry = await setup_deconz_integration( - hass, - aioclient_mock, - options={CONF_ALLOW_DECONZ_GROUPS: False}, - get_state_response=data, - ) + data = { + "groups": { + "1": { + "id": "Light group id", + "name": "Light group", + "type": "LightGroup", + "state": {"all_on": False, "any_on": True}, + "action": {}, + "scenes": [], + "lights": ["1"], + }, + "2": { + "id": "Empty group id", + "name": "Empty group", + "type": "LightGroup", + "state": {}, + "action": {}, + "scenes": [], + "lights": [], + }, + }, + "lights": { + "1": { + "ctmax": 454, + "ctmin": 155, + "name": "Tunable white light", + "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, + "type": "Tunable white light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration( + hass, + aioclient_mock, + options={CONF_ALLOW_DECONZ_GROUPS: False}, + ) - assert len(hass.states.async_all()) == 5 - assert hass.states.get("light.rgb_light") + assert len(hass.states.async_all()) == 1 assert hass.states.get("light.tunable_white_light") - assert hass.states.get("light.light_group") is None - assert hass.states.get("light.empty_group") is None + assert not hass.states.get("light.light_group") + assert not hass.states.get("light.empty_group") hass.config_entries.async_update_entry( config_entry, options={CONF_ALLOW_DECONZ_GROUPS: True} ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 6 + assert len(hass.states.async_all()) == 2 assert hass.states.get("light.light_group") hass.config_entries.async_update_entry( @@ -312,62 +328,96 @@ async def test_disable_light_groups(hass, aioclient_mock): ) await hass.async_block_till_done() - assert len(hass.states.async_all()) == 5 - assert hass.states.get("light.light_group") is None + assert len(hass.states.async_all()) == 1 + assert not hass.states.get("light.light_group") async def test_configuration_tool(hass, aioclient_mock): - """Test that lights or groups entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = { - "0": { - "etag": "26839cb118f5bf7ba1f2108256644010", - "hascolor": False, - "lastannounced": None, - "lastseen": "2020-11-22T11:27Z", - "manufacturername": "dresden elektronik", - "modelid": "ConBee II", - "name": "Configuration tool 1", - "state": {"reachable": True}, - "swversion": "0x264a0700", - "type": "Configuration tool", - "uniqueid": "00:21:2e:ff:ff:05:a7:a3-01", + """Test that configuration tool is not created.""" + data = { + "lights": { + "0": { + "etag": "26839cb118f5bf7ba1f2108256644010", + "hascolor": False, + "lastannounced": None, + "lastseen": "2020-11-22T11:27Z", + "manufacturername": "dresden elektronik", + "modelid": "ConBee II", + "name": "Configuration tool 1", + "state": {"reachable": True}, + "swversion": "0x264a0700", + "type": "Configuration tool", + "uniqueid": "00:21:2e:ff:ff:05:a7:a3-01", + } } } - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 0 +async def test_ikea_default_transition_time(hass, aioclient_mock): + """Verify that service calls to IKEA lights always extend with transition tinme 0 if absent.""" + data = { + "lights": { + "1": { + "manufacturername": "IKEA", + "name": "Dimmable light", + "state": {"on": True, "bri": 255, "reachable": True}, + "type": "Dimmable light", + "uniqueid": "00:00:00:00:00:00:00:01-00", + }, + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) + + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") + + await hass.services.async_call( + LIGHT_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: "light.dimmable_light", ATTR_BRIGHTNESS: 100}, + blocking=True, + ) + assert aioclient_mock.mock_calls[1][2] == { + "bri": 100, + "on": True, + "transitiontime": 0, + } + + async def test_lidl_christmas_light(hass, aioclient_mock): """Test that lights or groups entities are created.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["lights"] = { - "0": { - "etag": "87a89542bf9b9d0aa8134919056844f8", - "hascolor": True, - "lastannounced": None, - "lastseen": "2020-12-05T22:57Z", - "manufacturername": "_TZE200_s8gkrkxk", - "modelid": "TS0601", - "name": "xmas light", - "state": { - "bri": 25, - "colormode": "hs", - "effect": "none", - "hue": 53691, - "on": True, - "reachable": True, - "sat": 141, - }, - "swversion": None, - "type": "Color dimmable light", - "uniqueid": "58:8e:81:ff:fe:db:7b:be-01", + data = { + "lights": { + "0": { + "etag": "87a89542bf9b9d0aa8134919056844f8", + "hascolor": True, + "lastannounced": None, + "lastseen": "2020-12-05T22:57Z", + "manufacturername": "_TZE200_s8gkrkxk", + "modelid": "TS0601", + "name": "xmas light", + "state": { + "bri": 25, + "colormode": "hs", + "effect": "none", + "hue": 53691, + "on": True, + "reachable": True, + "sat": 141, + }, + "swversion": None, + "type": "Color dimmable light", + "uniqueid": "58:8e:81:ff:fe:db:7b:be-01", + } } } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) + + with patch.dict(DECONZ_WEB_REQUEST, data): + config_entry = await setup_deconz_integration(hass, aioclient_mock) mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/0/state") @@ -385,100 +435,98 @@ async def test_lidl_christmas_light(hass, aioclient_mock): assert hass.states.get("light.xmas_light") -async def test_non_color_light_reports_color(hass, aioclient_mock): +async def test_non_color_light_reports_color( + hass, aioclient_mock, mock_deconz_websocket +): """Verify hs_color does not crash when a group gets updated with a bad color value. After calling a scene color temp light of certain manufacturers report color temp in color space. """ - data = deepcopy(DECONZ_WEB_REQUEST) - - data["groups"] = { - "0": { - "action": { - "alert": "none", - "bri": 127, - "colormode": "hs", - "ct": 0, - "effect": "none", - "hue": 0, - "on": True, - "sat": 127, - "scene": None, - "xy": [0, 0], - }, - "devicemembership": [], - "etag": "81e42cf1b47affb72fa72bc2e25ba8bf", - "id": "0", - "lights": ["0", "1"], - "name": "All", - "scenes": [], - "state": {"all_on": False, "any_on": True}, - "type": "LightGroup", - } - } - - data["lights"] = { - "0": { - "ctmax": 500, - "ctmin": 153, - "etag": "026bcfe544ad76c7534e5ca8ed39047c", - "hascolor": True, - "manufacturername": "dresden elektronik", - "modelid": "FLS-PP3", - "name": "Light 1", - "pointsymbol": {}, - "state": { - "alert": None, - "bri": 111, - "colormode": "ct", - "ct": 307, - "effect": None, + data = { + "groups": { + "0": { + "action": { + "alert": "none", + "bri": 127, + "colormode": "hs", + "ct": 0, + "effect": "none", + "hue": 0, + "on": True, + "sat": 127, + "scene": None, + "xy": [0, 0], + }, + "devicemembership": [], + "etag": "81e42cf1b47affb72fa72bc2e25ba8bf", + "lights": ["0", "1"], + "name": "All", + "scenes": [], + "state": {"all_on": False, "any_on": True}, + "type": "LightGroup", + } + }, + "lights": { + "0": { + "ctmax": 500, + "ctmin": 153, + "etag": "026bcfe544ad76c7534e5ca8ed39047c", "hascolor": True, - "hue": 7998, - "on": False, - "reachable": True, - "sat": 172, - "xy": [0.421253, 0.39921], + "manufacturername": "dresden elektronik", + "modelid": "FLS-PP3", + "name": "Light 1", + "pointsymbol": {}, + "state": { + "alert": None, + "bri": 111, + "colormode": "ct", + "ct": 307, + "effect": None, + "hascolor": True, + "hue": 7998, + "on": False, + "reachable": True, + "sat": 172, + "xy": [0.421253, 0.39921], + }, + "swversion": "020C.201000A0", + "type": "Extended color light", + "uniqueid": "00:21:2E:FF:FF:EE:DD:CC-0A", }, - "swversion": "020C.201000A0", - "type": "Extended color light", - "uniqueid": "00:21:2E:FF:FF:EE:DD:CC-0A", - }, - "1": { - "colorcapabilities": 0, - "ctmax": 65535, - "ctmin": 0, - "etag": "9dd510cd474791481f189d2a68a3c7f1", - "hascolor": True, - "lastannounced": "2020-12-17T17:44:38Z", - "lastseen": "2021-01-11T18:36Z", - "manufacturername": "IKEA of Sweden", - "modelid": "TRADFRI bulb E27 WS opal 1000lm", - "name": "Küchenlicht", - "state": { - "alert": "none", - "bri": 156, - "colormode": "ct", - "ct": 250, - "on": True, - "reachable": True, + "1": { + "colorcapabilities": 0, + "ctmax": 65535, + "ctmin": 0, + "etag": "9dd510cd474791481f189d2a68a3c7f1", + "hascolor": True, + "lastannounced": "2020-12-17T17:44:38Z", + "lastseen": "2021-01-11T18:36Z", + "manufacturername": "IKEA of Sweden", + "modelid": "TRADFRI bulb E27 WS opal 1000lm", + "name": "Küchenlicht", + "state": { + "alert": "none", + "bri": 156, + "colormode": "ct", + "ct": 250, + "on": True, + "reachable": True, + }, + "swversion": "2.0.022", + "type": "Color temperature light", + "uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01", }, - "swversion": "2.0.022", - "type": "Color temperature light", - "uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01", }, } - config_entry = await setup_deconz_integration( - hass, aioclient_mock, get_state_response=data - ) - gateway = get_gateway_from_config_entry(hass, config_entry) + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 3 assert hass.states.get("light.all").attributes[ATTR_COLOR_TEMP] == 307 # Updating a scene will return a faulty color value for a non-color light causing an exception in hs_color - state_changed_event = { + event_changed_light = { "e": "changed", "id": "1", "r": "lights", @@ -493,7 +541,7 @@ async def test_non_color_light_reports_color(hass, aioclient_mock): "t": "event", "uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01", } - gateway.api.event_handler(state_changed_event) + await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() # Bug is fixed if we reach this point, but device won't have neither color temp nor color @@ -504,9 +552,8 @@ async def test_non_color_light_reports_color(hass, aioclient_mock): async def test_verify_group_supported_features(hass, aioclient_mock): """Test that group supported features reflect what included lights support.""" - data = deepcopy(DECONZ_WEB_REQUEST) - data["groups"] = deepcopy( - { + data = { + "groups": { "1": { "id": "Group1", "name": "group", @@ -516,19 +563,15 @@ async def test_verify_group_supported_features(hass, aioclient_mock): "scenes": [], "lights": ["1", "2", "3"], }, - } - ) - data["lights"] = deepcopy( - { + }, + "lights": { "1": { - "id": "light1", "name": "Dimmable light", "state": {"on": True, "bri": 255, "reachable": True}, "type": "Light", "uniqueid": "00:00:00:00:00:00:00:01-00", }, "2": { - "id": "light2", "name": "Color light", "state": { "on": True, @@ -544,18 +587,17 @@ async def test_verify_group_supported_features(hass, aioclient_mock): "3": { "ctmax": 454, "ctmin": 155, - "id": "light3", "name": "Tunable light", "state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True}, "type": "Tunable white light", "uniqueid": "00:00:00:00:00:00:00:03-00", }, - } - ) - await setup_deconz_integration(hass, aioclient_mock, get_state_response=data) + }, + } + with patch.dict(DECONZ_WEB_REQUEST, data): + await setup_deconz_integration(hass, aioclient_mock) assert len(hass.states.async_all()) == 4 - group = hass.states.get("light.group") - assert group.state == STATE_ON - assert group.attributes[ATTR_SUPPORTED_FEATURES] == 63 + assert hass.states.get("light.group").state == STATE_ON + assert hass.states.get("light.group").attributes[ATTR_SUPPORTED_FEATURES] == 63