2021-01-15 14:17:40 +00:00
|
|
|
"""Test the Z-Wave JS Websocket API."""
|
2021-02-19 23:44:15 +00:00
|
|
|
import json
|
2021-01-26 09:27:20 +00:00
|
|
|
from unittest.mock import patch
|
|
|
|
|
2021-02-22 23:35:19 +00:00
|
|
|
from zwave_js_server.const import LogLevel
|
2021-01-18 15:08:52 +00:00
|
|
|
from zwave_js_server.event import Event
|
2021-02-23 01:34:47 +00:00
|
|
|
from zwave_js_server.exceptions import InvalidNewValue, NotFoundError, SetValueFailed
|
2021-01-15 14:17:40 +00:00
|
|
|
|
2021-02-26 15:07:50 +00:00
|
|
|
from homeassistant.components.websocket_api.const import ERR_NOT_FOUND
|
2021-02-22 23:35:19 +00:00
|
|
|
from homeassistant.components.zwave_js.api import (
|
|
|
|
CONFIG,
|
|
|
|
ENABLED,
|
|
|
|
ENTRY_ID,
|
|
|
|
FILENAME,
|
|
|
|
FORCE_CONSOLE,
|
|
|
|
ID,
|
|
|
|
LEVEL,
|
|
|
|
LOG_TO_FILE,
|
|
|
|
NODE_ID,
|
2021-02-23 01:34:47 +00:00
|
|
|
PROPERTY,
|
|
|
|
PROPERTY_KEY,
|
2021-02-22 23:35:19 +00:00
|
|
|
TYPE,
|
2021-02-23 01:34:47 +00:00
|
|
|
VALUE,
|
2021-02-22 23:35:19 +00:00
|
|
|
)
|
2021-01-18 15:08:52 +00:00
|
|
|
from homeassistant.components.zwave_js.const import DOMAIN
|
|
|
|
from homeassistant.helpers.device_registry import async_get_registry
|
2021-01-15 14:17:40 +00:00
|
|
|
|
|
|
|
|
2021-01-19 15:41:24 +00:00
|
|
|
async def test_websocket_api(hass, integration, multisensor_6, hass_ws_client):
|
|
|
|
"""Test the network and node status websocket commands."""
|
2021-01-15 14:17:40 +00:00
|
|
|
entry = integration
|
|
|
|
ws_client = await hass_ws_client(hass)
|
|
|
|
|
|
|
|
await ws_client.send_json(
|
|
|
|
{ID: 2, TYPE: "zwave_js/network_status", ENTRY_ID: entry.entry_id}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
result = msg["result"]
|
|
|
|
|
|
|
|
assert result["client"]["ws_server_url"] == "ws://test:3000/zjs"
|
|
|
|
assert result["client"]["server_version"] == "1.0.0"
|
2021-01-18 15:08:52 +00:00
|
|
|
|
2021-01-19 15:41:24 +00:00
|
|
|
node = multisensor_6
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 3,
|
|
|
|
TYPE: "zwave_js/node_status",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
NODE_ID: node.node_id,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
result = msg["result"]
|
|
|
|
|
|
|
|
assert result[NODE_ID] == 52
|
|
|
|
assert result["ready"]
|
|
|
|
assert result["is_routing"]
|
|
|
|
assert not result["is_secure"]
|
|
|
|
assert result["status"] == 1
|
|
|
|
|
2021-02-22 22:03:38 +00:00
|
|
|
# Test getting configuration parameter values
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 4,
|
|
|
|
TYPE: "zwave_js/get_config_parameters",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
NODE_ID: node.node_id,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
result = msg["result"]
|
|
|
|
|
|
|
|
assert len(result) == 61
|
|
|
|
key = "52-112-0-2-00-00"
|
|
|
|
assert result[key]["property"] == 2
|
|
|
|
assert result[key]["metadata"]["type"] == "number"
|
|
|
|
assert result[key]["configuration_value_type"] == "enumerated"
|
2021-02-23 14:31:47 +00:00
|
|
|
assert result[key]["metadata"]["states"]
|
2021-02-22 22:03:38 +00:00
|
|
|
|
2021-02-26 15:07:50 +00:00
|
|
|
# Test getting non-existent node fails
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 5,
|
|
|
|
TYPE: "zwave_js/node_status",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
NODE_ID: 99999,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert not msg["success"]
|
|
|
|
assert msg["error"]["code"] == ERR_NOT_FOUND
|
|
|
|
|
|
|
|
# Test getting non-existent node config params fails
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 6,
|
|
|
|
TYPE: "zwave_js/get_config_parameters",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
NODE_ID: 99999,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert not msg["success"]
|
|
|
|
assert msg["error"]["code"] == ERR_NOT_FOUND
|
|
|
|
|
2021-01-18 15:08:52 +00:00
|
|
|
|
|
|
|
async def test_add_node(
|
|
|
|
hass, integration, client, hass_ws_client, nortek_thermostat_added_event
|
|
|
|
):
|
|
|
|
"""Test the add_node websocket command."""
|
|
|
|
entry = integration
|
|
|
|
ws_client = await hass_ws_client(hass)
|
|
|
|
|
|
|
|
client.async_send_command.return_value = {"success": True}
|
|
|
|
|
|
|
|
await ws_client.send_json(
|
|
|
|
{ID: 3, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id}
|
|
|
|
)
|
|
|
|
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["success"]
|
|
|
|
|
|
|
|
event = Event(
|
|
|
|
type="inclusion started",
|
|
|
|
data={
|
|
|
|
"source": "controller",
|
|
|
|
"event": "inclusion started",
|
|
|
|
"secure": False,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
client.driver.receive_event(event)
|
|
|
|
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["event"]["event"] == "inclusion started"
|
|
|
|
|
|
|
|
client.driver.receive_event(nortek_thermostat_added_event)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["event"]["event"] == "node added"
|
|
|
|
|
|
|
|
|
|
|
|
async def test_cancel_inclusion_exclusion(hass, integration, client, hass_ws_client):
|
|
|
|
"""Test cancelling the inclusion and exclusion process."""
|
|
|
|
entry = integration
|
|
|
|
ws_client = await hass_ws_client(hass)
|
|
|
|
|
|
|
|
client.async_send_command.return_value = {"success": True}
|
|
|
|
|
|
|
|
await ws_client.send_json(
|
|
|
|
{ID: 4, TYPE: "zwave_js/stop_inclusion", ENTRY_ID: entry.entry_id}
|
|
|
|
)
|
|
|
|
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["success"]
|
|
|
|
|
|
|
|
await ws_client.send_json(
|
|
|
|
{ID: 5, TYPE: "zwave_js/stop_exclusion", ENTRY_ID: entry.entry_id}
|
|
|
|
)
|
|
|
|
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["success"]
|
|
|
|
|
|
|
|
|
|
|
|
async def test_remove_node(
|
|
|
|
hass,
|
|
|
|
integration,
|
|
|
|
client,
|
|
|
|
hass_ws_client,
|
|
|
|
nortek_thermostat,
|
|
|
|
nortek_thermostat_removed_event,
|
|
|
|
):
|
|
|
|
"""Test the remove_node websocket command."""
|
|
|
|
entry = integration
|
|
|
|
ws_client = await hass_ws_client(hass)
|
|
|
|
|
|
|
|
client.async_send_command.return_value = {"success": True}
|
|
|
|
|
|
|
|
await ws_client.send_json(
|
|
|
|
{ID: 3, TYPE: "zwave_js/remove_node", ENTRY_ID: entry.entry_id}
|
|
|
|
)
|
|
|
|
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["success"]
|
|
|
|
|
|
|
|
event = Event(
|
|
|
|
type="exclusion started",
|
|
|
|
data={
|
|
|
|
"source": "controller",
|
|
|
|
"event": "exclusion started",
|
|
|
|
"secure": False,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
client.driver.receive_event(event)
|
|
|
|
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["event"]["event"] == "exclusion started"
|
|
|
|
|
|
|
|
# Add mock node to controller
|
|
|
|
client.driver.controller.nodes[67] = nortek_thermostat
|
|
|
|
|
|
|
|
dev_reg = await async_get_registry(hass)
|
|
|
|
|
|
|
|
# Create device registry entry for mock node
|
|
|
|
device = dev_reg.async_get_or_create(
|
|
|
|
config_entry_id=entry.entry_id,
|
|
|
|
identifiers={(DOMAIN, "3245146787-67")},
|
|
|
|
name="Node 67",
|
|
|
|
)
|
|
|
|
|
|
|
|
# Fire node removed event
|
|
|
|
client.driver.receive_event(nortek_thermostat_removed_event)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["event"]["event"] == "node removed"
|
|
|
|
|
|
|
|
# Verify device was removed from device registry
|
|
|
|
device = dev_reg.async_get_device(
|
|
|
|
identifiers={(DOMAIN, "3245146787-67")},
|
|
|
|
)
|
|
|
|
assert device is None
|
2021-01-26 09:27:20 +00:00
|
|
|
|
|
|
|
|
2021-02-23 01:34:47 +00:00
|
|
|
async def test_set_config_parameter(
|
|
|
|
hass, client, hass_ws_client, multisensor_6, integration
|
|
|
|
):
|
|
|
|
"""Test the set_config_parameter service."""
|
|
|
|
entry = integration
|
|
|
|
ws_client = await hass_ws_client(hass)
|
|
|
|
|
|
|
|
client.async_send_command.return_value = {"success": True}
|
|
|
|
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 1,
|
|
|
|
TYPE: "zwave_js/set_config_parameter",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
NODE_ID: 52,
|
|
|
|
PROPERTY: 102,
|
|
|
|
PROPERTY_KEY: 1,
|
|
|
|
VALUE: 1,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["result"]
|
|
|
|
|
|
|
|
assert len(client.async_send_command.call_args_list) == 1
|
|
|
|
args = client.async_send_command.call_args[0][0]
|
|
|
|
assert args["command"] == "node.set_value"
|
|
|
|
assert args["nodeId"] == 52
|
|
|
|
assert args["valueId"] == {
|
|
|
|
"commandClassName": "Configuration",
|
|
|
|
"commandClass": 112,
|
|
|
|
"endpoint": 0,
|
|
|
|
"property": 102,
|
|
|
|
"propertyName": "Group 2: Send battery reports",
|
|
|
|
"propertyKey": 1,
|
|
|
|
"metadata": {
|
|
|
|
"type": "number",
|
|
|
|
"readable": True,
|
|
|
|
"writeable": True,
|
|
|
|
"valueSize": 4,
|
|
|
|
"min": 0,
|
|
|
|
"max": 1,
|
|
|
|
"default": 1,
|
|
|
|
"format": 0,
|
|
|
|
"allowManualEntry": True,
|
|
|
|
"label": "Group 2: Send battery reports",
|
|
|
|
"description": "Include battery information in periodic reports to Group 2",
|
|
|
|
"isFromConfig": True,
|
|
|
|
},
|
|
|
|
"value": 0,
|
|
|
|
}
|
|
|
|
assert args["value"] == 1
|
|
|
|
|
|
|
|
client.async_send_command.reset_mock()
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
"homeassistant.components.zwave_js.api.async_set_config_parameter",
|
|
|
|
) as set_param_mock:
|
|
|
|
set_param_mock.side_effect = InvalidNewValue("test")
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 2,
|
|
|
|
TYPE: "zwave_js/set_config_parameter",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
NODE_ID: 52,
|
|
|
|
PROPERTY: 102,
|
|
|
|
PROPERTY_KEY: 1,
|
|
|
|
VALUE: 1,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
|
|
|
|
assert len(client.async_send_command.call_args_list) == 0
|
|
|
|
assert not msg["success"]
|
|
|
|
assert msg["error"]["code"] == "not_supported"
|
|
|
|
assert msg["error"]["message"] == "test"
|
|
|
|
|
|
|
|
set_param_mock.side_effect = NotFoundError("test")
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 3,
|
|
|
|
TYPE: "zwave_js/set_config_parameter",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
NODE_ID: 52,
|
|
|
|
PROPERTY: 102,
|
|
|
|
PROPERTY_KEY: 1,
|
|
|
|
VALUE: 1,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
|
|
|
|
assert len(client.async_send_command.call_args_list) == 0
|
|
|
|
assert not msg["success"]
|
|
|
|
assert msg["error"]["code"] == "not_found"
|
|
|
|
assert msg["error"]["message"] == "test"
|
|
|
|
|
|
|
|
set_param_mock.side_effect = SetValueFailed("test")
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 4,
|
|
|
|
TYPE: "zwave_js/set_config_parameter",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
NODE_ID: 52,
|
|
|
|
PROPERTY: 102,
|
|
|
|
PROPERTY_KEY: 1,
|
|
|
|
VALUE: 1,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
|
|
|
|
assert len(client.async_send_command.call_args_list) == 0
|
|
|
|
assert not msg["success"]
|
|
|
|
assert msg["error"]["code"] == "unknown_error"
|
|
|
|
assert msg["error"]["message"] == "test"
|
|
|
|
|
|
|
|
|
2021-01-26 09:27:20 +00:00
|
|
|
async def test_dump_view(integration, hass_client):
|
|
|
|
"""Test the HTTP dump view."""
|
|
|
|
client = await hass_client()
|
|
|
|
with patch(
|
|
|
|
"zwave_js_server.dump.dump_msgs",
|
|
|
|
return_value=[{"hello": "world"}, {"second": "msg"}],
|
|
|
|
):
|
|
|
|
resp = await client.get(f"/api/zwave_js/dump/{integration.entry_id}")
|
|
|
|
assert resp.status == 200
|
2021-02-19 23:44:15 +00:00
|
|
|
assert json.loads(await resp.text()) == [{"hello": "world"}, {"second": "msg"}]
|
2021-01-26 09:27:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_dump_view_invalid_entry_id(integration, hass_client):
|
|
|
|
"""Test an invalid config entry id parameter."""
|
|
|
|
client = await hass_client()
|
|
|
|
resp = await client.get("/api/zwave_js/dump/INVALID")
|
|
|
|
assert resp.status == 400
|
2021-02-22 23:35:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_update_log_config(hass, client, integration, hass_ws_client):
|
|
|
|
"""Test that the update_log_config WS API call works and that schema validation works."""
|
|
|
|
entry = integration
|
|
|
|
ws_client = await hass_ws_client(hass)
|
|
|
|
|
|
|
|
# Test we can set log level
|
|
|
|
client.async_send_command.return_value = {"success": True}
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 1,
|
|
|
|
TYPE: "zwave_js/update_log_config",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
CONFIG: {LEVEL: "Error"},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["success"]
|
|
|
|
|
|
|
|
assert len(client.async_send_command.call_args_list) == 1
|
|
|
|
args = client.async_send_command.call_args[0][0]
|
|
|
|
assert args["command"] == "update_log_config"
|
|
|
|
assert args["config"] == {"level": 0}
|
|
|
|
|
|
|
|
client.async_send_command.reset_mock()
|
|
|
|
|
|
|
|
# Test we can set logToFile to True
|
|
|
|
client.async_send_command.return_value = {"success": True}
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 2,
|
|
|
|
TYPE: "zwave_js/update_log_config",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
CONFIG: {LOG_TO_FILE: True, FILENAME: "/test"},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["success"]
|
|
|
|
|
|
|
|
assert len(client.async_send_command.call_args_list) == 1
|
|
|
|
args = client.async_send_command.call_args[0][0]
|
|
|
|
assert args["command"] == "update_log_config"
|
|
|
|
assert args["config"] == {"logToFile": True, "filename": "/test"}
|
|
|
|
|
|
|
|
client.async_send_command.reset_mock()
|
|
|
|
|
|
|
|
# Test all parameters
|
|
|
|
client.async_send_command.return_value = {"success": True}
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 3,
|
|
|
|
TYPE: "zwave_js/update_log_config",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
CONFIG: {
|
|
|
|
LEVEL: "Error",
|
|
|
|
LOG_TO_FILE: True,
|
|
|
|
FILENAME: "/test",
|
|
|
|
FORCE_CONSOLE: True,
|
|
|
|
ENABLED: True,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["success"]
|
|
|
|
|
|
|
|
assert len(client.async_send_command.call_args_list) == 1
|
|
|
|
args = client.async_send_command.call_args[0][0]
|
|
|
|
assert args["command"] == "update_log_config"
|
|
|
|
assert args["config"] == {
|
|
|
|
"level": 0,
|
|
|
|
"logToFile": True,
|
|
|
|
"filename": "/test",
|
|
|
|
"forceConsole": True,
|
|
|
|
"enabled": True,
|
|
|
|
}
|
|
|
|
|
|
|
|
client.async_send_command.reset_mock()
|
|
|
|
|
|
|
|
# Test error when setting unrecognized log level
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 4,
|
|
|
|
TYPE: "zwave_js/update_log_config",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
CONFIG: {LEVEL: "bad_log_level"},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert not msg["success"]
|
|
|
|
assert "error" in msg and "value must be one of" in msg["error"]["message"]
|
|
|
|
|
|
|
|
# Test error without service data
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 5,
|
|
|
|
TYPE: "zwave_js/update_log_config",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
CONFIG: {},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert not msg["success"]
|
|
|
|
assert "error" in msg and "must contain at least one of" in msg["error"]["message"]
|
|
|
|
|
|
|
|
# Test error if we set logToFile to True without providing filename
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 6,
|
|
|
|
TYPE: "zwave_js/update_log_config",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
CONFIG: {LOG_TO_FILE: True},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert not msg["success"]
|
|
|
|
assert (
|
|
|
|
"error" in msg
|
|
|
|
and "must be provided if logging to file" in msg["error"]["message"]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_get_log_config(hass, client, integration, hass_ws_client):
|
|
|
|
"""Test that the get_log_config WS API call works."""
|
|
|
|
entry = integration
|
|
|
|
ws_client = await hass_ws_client(hass)
|
|
|
|
|
|
|
|
# Test we can get log configuration
|
|
|
|
client.async_send_command.return_value = {
|
|
|
|
"success": True,
|
|
|
|
"config": {
|
|
|
|
"enabled": True,
|
|
|
|
"level": 0,
|
|
|
|
"logToFile": False,
|
|
|
|
"filename": "/test.txt",
|
|
|
|
"forceConsole": False,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
await ws_client.send_json(
|
|
|
|
{
|
|
|
|
ID: 1,
|
|
|
|
TYPE: "zwave_js/get_log_config",
|
|
|
|
ENTRY_ID: entry.entry_id,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
msg = await ws_client.receive_json()
|
|
|
|
assert msg["result"]
|
|
|
|
assert msg["success"]
|
|
|
|
|
|
|
|
log_config = msg["result"]
|
|
|
|
assert log_config["enabled"]
|
|
|
|
assert log_config["level"] == LogLevel.ERROR
|
|
|
|
assert log_config["log_to_file"] is False
|
|
|
|
assert log_config["filename"] == "/test.txt"
|
|
|
|
assert log_config["force_console"] is False
|