Add zwave_js.multicast_set_value service (#51115)

* Add zwave_js.multicast_set_value service

* comment

* Add test for multiple config entries validation

* additional validation test

* brevity

* wrap schema in vol.Schema

* Update homeassistant/components/zwave_js/services.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* do node transform and multicast validation in schema validation

* move poll value entity validation into schema validation, pass helper functions dev and ent reg instead of retrieving it every time

* make validators nested functions since they don't neeed to be externally accessible

* Update homeassistant/components/zwave_js/services.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Remove errant ALLOW_EXTRA

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Raman Gupta 2021-05-27 21:57:35 -04:00 committed by GitHub
parent 93ada0a675
commit ca8d09e5e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 432 additions and 69 deletions

View file

@ -1,9 +1,12 @@
"""Test the Z-Wave JS services."""
from unittest.mock import MagicMock, patch
import pytest
import voluptuous as vol
from zwave_js_server.exceptions import SetValueFailed
from homeassistant.components.zwave_js.const import (
ATTR_BROADCAST,
ATTR_COMMAND_CLASS,
ATTR_CONFIG_PARAMETER,
ATTR_CONFIG_PARAMETER_BITMASK,
@ -14,6 +17,7 @@ from homeassistant.components.zwave_js.const import (
ATTR_WAIT_FOR_RESULT,
DOMAIN,
SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS,
SERVICE_MULTICAST_SET_VALUE,
SERVICE_REFRESH_VALUE,
SERVICE_SET_CONFIG_PARAMETER,
SERVICE_SET_VALUE,
@ -212,8 +216,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration):
}
assert args["value"] == 1
# Test that an invalid entity ID raises a ValueError
with pytest.raises(ValueError):
# Test that an invalid entity ID raises a MultipleInvalid
with pytest.raises(vol.MultipleInvalid):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_CONFIG_PARAMETER,
@ -225,8 +229,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration):
blocking=True,
)
# Test that an invalid device ID raises a ValueError
with pytest.raises(ValueError):
# Test that an invalid device ID raises a MultipleInvalid
with pytest.raises(vol.MultipleInvalid):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_CONFIG_PARAMETER,
@ -259,8 +263,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration):
identifiers={("test", "test")},
)
# Test that a non Z-Wave JS device raises a ValueError
with pytest.raises(ValueError):
# Test that a non Z-Wave JS device raises a MultipleInvalid
with pytest.raises(vol.MultipleInvalid):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_CONFIG_PARAMETER,
@ -276,8 +280,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration):
config_entry_id=integration.entry_id, identifiers={(DOMAIN, "500-500")}
)
# Test that a Z-Wave JS device with an invalid node ID raises a ValueError
with pytest.raises(ValueError):
# Test that a Z-Wave JS device with an invalid node ID raises a MultipleInvalid
with pytest.raises(vol.MultipleInvalid):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_CONFIG_PARAMETER,
@ -297,8 +301,8 @@ async def test_set_config_parameter(hass, client, multisensor_6, integration):
config_entry=non_zwave_js_config_entry,
)
# Test that a non Z-Wave JS entity raises a ValueError
with pytest.raises(ValueError):
# Test that a non Z-Wave JS entity raises a MultipleInvalid
with pytest.raises(vol.MultipleInvalid):
await hass.services.async_call(
DOMAIN,
SERVICE_SET_CONFIG_PARAMETER,
@ -530,8 +534,8 @@ async def test_poll_value(
)
assert len(client.async_send_command.call_args_list) == 8
# Test polling against an invalid entity raises ValueError
with pytest.raises(ValueError):
# Test polling against an invalid entity raises MultipleInvalid
with pytest.raises(vol.MultipleInvalid):
await hass.services.async_call(
DOMAIN,
SERVICE_REFRESH_VALUE,
@ -634,3 +638,155 @@ async def test_set_value(hass, client, climate_danfoss_lc_13, integration):
},
blocking=True,
)
async def test_multicast_set_value(
hass,
client,
climate_danfoss_lc_13,
climate_radio_thermostat_ct100_plus_different_endpoints,
integration,
):
"""Test multicast_set_value service."""
# Test successful multicast call
await hass.services.async_call(
DOMAIN,
SERVICE_MULTICAST_SET_VALUE,
{
ATTR_ENTITY_ID: [
CLIMATE_DANFOSS_LC13_ENTITY,
CLIMATE_RADIO_THERMOSTAT_ENTITY,
],
ATTR_COMMAND_CLASS: 117,
ATTR_PROPERTY: "local",
ATTR_VALUE: 2,
},
blocking=True,
)
assert len(client.async_send_command.call_args_list) == 1
args = client.async_send_command.call_args[0][0]
assert args["command"] == "multicast_group.set_value"
assert args["nodeIDs"] == [
climate_radio_thermostat_ct100_plus_different_endpoints.node_id,
climate_danfoss_lc_13.node_id,
]
assert args["valueId"] == {
"commandClass": 117,
"property": "local",
}
assert args["value"] == 2
client.async_send_command.reset_mock()
# Test successful broadcast call
await hass.services.async_call(
DOMAIN,
SERVICE_MULTICAST_SET_VALUE,
{
ATTR_BROADCAST: True,
ATTR_COMMAND_CLASS: 117,
ATTR_PROPERTY: "local",
ATTR_VALUE: 2,
},
blocking=True,
)
assert len(client.async_send_command.call_args_list) == 1
args = client.async_send_command.call_args[0][0]
assert args["command"] == "broadcast_node.set_value"
assert args["valueId"] == {
"commandClass": 117,
"property": "local",
}
assert args["value"] == 2
client.async_send_command.reset_mock()
# Test sending one node without broadcast fails
with pytest.raises(vol.Invalid):
await hass.services.async_call(
DOMAIN,
SERVICE_MULTICAST_SET_VALUE,
{
ATTR_ENTITY_ID: CLIMATE_DANFOSS_LC13_ENTITY,
ATTR_COMMAND_CLASS: 117,
ATTR_PROPERTY: "local",
ATTR_VALUE: 2,
},
blocking=True,
)
# Test no device, entity, or broadcast flag raises error
with pytest.raises(vol.Invalid):
await hass.services.async_call(
DOMAIN,
SERVICE_MULTICAST_SET_VALUE,
{
ATTR_COMMAND_CLASS: 117,
ATTR_PROPERTY: "local",
ATTR_VALUE: 2,
},
blocking=True,
)
# Test that when a command fails we raise an exception
client.async_send_command.return_value = {"success": False}
with pytest.raises(SetValueFailed):
await hass.services.async_call(
DOMAIN,
SERVICE_MULTICAST_SET_VALUE,
{
ATTR_ENTITY_ID: [
CLIMATE_DANFOSS_LC13_ENTITY,
CLIMATE_RADIO_THERMOSTAT_ENTITY,
],
ATTR_COMMAND_CLASS: 117,
ATTR_PROPERTY: "local",
ATTR_VALUE: 2,
},
blocking=True,
)
# Create a fake node with a different home ID from a real node and patch it into
# return of helper function to check the validation for two nodes having different
# home IDs
diff_network_node = MagicMock()
diff_network_node.client.driver.controller.home_id.return_value = "diff_home_id"
with pytest.raises(vol.MultipleInvalid), patch(
"homeassistant.components.zwave_js.services.async_get_node_from_device_id",
return_value=diff_network_node,
):
await hass.services.async_call(
DOMAIN,
SERVICE_MULTICAST_SET_VALUE,
{
ATTR_ENTITY_ID: [
CLIMATE_DANFOSS_LC13_ENTITY,
],
ATTR_DEVICE_ID: "fake_device_id",
ATTR_COMMAND_CLASS: 117,
ATTR_PROPERTY: "local",
ATTR_VALUE: 2,
},
blocking=True,
)
# Test that when there are multiple zwave_js config entries, service will fail
# without devices or entities
new_entry = MockConfigEntry(domain=DOMAIN)
new_entry.add_to_hass(hass)
with pytest.raises(vol.Invalid):
await hass.services.async_call(
DOMAIN,
SERVICE_MULTICAST_SET_VALUE,
{
ATTR_BROADCAST: True,
ATTR_COMMAND_CLASS: 117,
ATTR_PROPERTY: "local",
ATTR_VALUE: 2,
},
blocking=True,
)