Handle FailedCommand exceptions in zwave_js WS API (#52461)

* Handle zwave-js errors in WS API

* Unsubscribe callbacks when zwave-js error is caught

* fix tests

* simplify unsub logic

* add tests

* add kwargs to be safe

* use existing msg format

* switch to generic failed command handling

* remove unneeded constant

* Update homeassistant/components/zwave_js/api.py

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

* Update homeassistant/components/zwave_js/api.py

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

* fix

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Raman Gupta 2021-07-13 00:13:43 -04:00 committed by GitHub
parent 92e4013f73
commit e915f5be53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 507 additions and 31 deletions

View file

@ -4,7 +4,7 @@ from __future__ import annotations
import dataclasses
from functools import partial, wraps
import json
from typing import Callable
from typing import Any, Callable
from aiohttp import hdrs, web, web_exceptions, web_request
import voluptuous as vol
@ -13,6 +13,7 @@ from zwave_js_server.client import Client
from zwave_js_server.const import CommandClass, LogLevel
from zwave_js_server.exceptions import (
BaseZwaveJSServerError,
FailedCommand,
InvalidNewValue,
NotFoundError,
SetValueFailed,
@ -47,6 +48,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import (
CONF_DATA_COLLECTION_OPTED_IN,
DATA_CLIENT,
DATA_UNSUBSCRIBE,
DOMAIN,
EVENT_DEVICE_ADDED_TO_REGISTRY,
)
@ -134,6 +136,30 @@ def async_get_node(orig_func: Callable) -> Callable:
return async_get_node_func
def async_handle_failed_command(orig_func: Callable) -> Callable:
"""Decorate async function to handle FailedCommand and send relevant error."""
@wraps(orig_func)
async def async_handle_failed_command_func(
hass: HomeAssistant,
connection: ActiveConnection,
msg: dict,
*args: Any,
**kwargs: Any,
) -> None:
"""Handle FailedCommand within function and send relevant error."""
try:
await orig_func(hass, connection, msg, *args, **kwargs)
except FailedCommand as err:
# Unsubscribe to callbacks
if unsubs := msg.get(DATA_UNSUBSCRIBE):
for unsub in unsubs:
unsub()
connection.send_error(msg[ID], err.error_code, err.args[0])
return async_handle_failed_command_func
@callback
def async_register_api(hass: HomeAssistant) -> None:
"""Register all of our api endpoints."""
@ -318,6 +344,7 @@ async def websocket_node_metadata(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_node
async def websocket_ping_node(
hass: HomeAssistant,
@ -342,6 +369,7 @@ async def websocket_ping_node(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_add_node(
hass: HomeAssistant,
@ -410,7 +438,7 @@ async def websocket_add_node(
)
connection.subscriptions[msg["id"]] = async_cleanup
unsubs = [
msg[DATA_UNSUBSCRIBE] = unsubs = [
controller.on("inclusion started", forward_event),
controller.on("inclusion failed", forward_event),
controller.on("inclusion stopped", forward_event),
@ -435,6 +463,7 @@ async def websocket_add_node(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_stop_inclusion(
hass: HomeAssistant,
@ -460,6 +489,7 @@ async def websocket_stop_inclusion(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_stop_exclusion(
hass: HomeAssistant,
@ -485,6 +515,7 @@ async def websocket_stop_exclusion(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_remove_node(
hass: HomeAssistant,
@ -522,7 +553,7 @@ async def websocket_remove_node(
)
connection.subscriptions[msg["id"]] = async_cleanup
unsubs = [
msg[DATA_UNSUBSCRIBE] = unsubs = [
controller.on("exclusion started", forward_event),
controller.on("exclusion failed", forward_event),
controller.on("exclusion stopped", forward_event),
@ -546,6 +577,7 @@ async def websocket_remove_node(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_replace_failed_node(
hass: HomeAssistant,
@ -628,7 +660,7 @@ async def websocket_replace_failed_node(
)
connection.subscriptions[msg["id"]] = async_cleanup
unsubs = [
msg[DATA_UNSUBSCRIBE] = unsubs = [
controller.on("inclusion started", forward_event),
controller.on("inclusion failed", forward_event),
controller.on("inclusion stopped", forward_event),
@ -655,6 +687,7 @@ async def websocket_replace_failed_node(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_remove_failed_node(
hass: HomeAssistant,
@ -670,7 +703,8 @@ async def websocket_remove_failed_node(
@callback
def async_cleanup() -> None:
"""Remove signal listeners."""
unsub()
for unsub in unsubs:
unsub()
@callback
def node_removed(event: dict) -> None:
@ -686,7 +720,7 @@ async def websocket_remove_failed_node(
)
connection.subscriptions[msg["id"]] = async_cleanup
unsub = controller.on("node removed", node_removed)
msg[DATA_UNSUBSCRIBE] = unsubs = [controller.on("node removed", node_removed)]
result = await controller.async_remove_failed_node(node_id)
connection.send_result(
@ -703,6 +737,7 @@ async def websocket_remove_failed_node(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_begin_healing_network(
hass: HomeAssistant,
@ -755,7 +790,7 @@ async def websocket_subscribe_heal_network_progress(
)
connection.subscriptions[msg["id"]] = async_cleanup
unsubs = [
msg[DATA_UNSUBSCRIBE] = unsubs = [
controller.on("heal network progress", partial(forward_event, "progress")),
controller.on("heal network done", partial(forward_event, "result")),
]
@ -771,6 +806,7 @@ async def websocket_subscribe_heal_network_progress(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_stop_healing_network(
hass: HomeAssistant,
@ -797,6 +833,7 @@ async def websocket_stop_healing_network(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_heal_node(
hass: HomeAssistant,
@ -824,6 +861,7 @@ async def websocket_heal_node(
},
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_node
async def websocket_refresh_node_info(
hass: HomeAssistant,
@ -854,7 +892,7 @@ async def websocket_refresh_node_info(
)
connection.subscriptions[msg["id"]] = async_cleanup
unsubs = [
msg[DATA_UNSUBSCRIBE] = unsubs = [
node.on("interview started", forward_event),
node.on("interview completed", forward_event),
node.on("interview stage completed", forward_stage),
@ -874,6 +912,7 @@ async def websocket_refresh_node_info(
},
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_node
async def websocket_refresh_node_values(
hass: HomeAssistant,
@ -896,6 +935,7 @@ async def websocket_refresh_node_values(
},
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_node
async def websocket_refresh_node_cc_values(
hass: HomeAssistant,
@ -930,6 +970,7 @@ async def websocket_refresh_node_cc_values(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_node
async def websocket_set_config_parameter(
hass: HomeAssistant,
@ -1027,6 +1068,7 @@ def filename_is_present_if_logging_to_file(obj: dict) -> dict:
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_subscribe_log_updates(
hass: HomeAssistant,
@ -1076,7 +1118,7 @@ async def websocket_subscribe_log_updates(
)
)
unsubs = [
msg[DATA_UNSUBSCRIBE] = unsubs = [
driver.on("logging", log_messages),
driver.on("log config updated", log_config_updates),
]
@ -1114,6 +1156,7 @@ async def websocket_subscribe_log_updates(
},
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_update_log_config(
hass: HomeAssistant,
@ -1161,6 +1204,7 @@ async def websocket_get_log_config(
},
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_update_data_collection_preference(
hass: HomeAssistant,
@ -1191,6 +1235,7 @@ async def websocket_update_data_collection_preference(
},
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_data_collection_status(
hass: HomeAssistant,
@ -1273,6 +1318,7 @@ async def websocket_version_info(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_node
async def websocket_abort_firmware_update(
hass: HomeAssistant,
@ -1337,7 +1383,7 @@ async def websocket_subscribe_firmware_update_status(
)
)
unsubs = [
msg[DATA_UNSUBSCRIBE] = unsubs = [
node.on("firmware update progress", forward_progress),
node.on("firmware update finished", forward_finished),
]
@ -1400,6 +1446,7 @@ class FirmwareUploadView(HomeAssistantView):
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_check_for_config_updates(
hass: HomeAssistant,
@ -1427,6 +1474,7 @@ async def websocket_check_for_config_updates(
}
)
@websocket_api.async_response
@async_handle_failed_command
@async_get_entry
async def websocket_install_config_update(
hass: HomeAssistant,

View file

@ -7,6 +7,7 @@ from zwave_js_server.const import LogLevel
from zwave_js_server.event import Event
from zwave_js_server.exceptions import (
FailedCommand,
FailedZWaveCommand,
InvalidNewValue,
NotFoundError,
SetValueFailed,
@ -280,13 +281,32 @@ async def test_ping_node(
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.node.Node.async_ping",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 5,
TYPE: "zwave_js/ping_node",
ENTRY_ID: entry.entry_id,
NODE_ID: node.node_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 5,
ID: 6,
TYPE: "zwave_js/ping_node",
ENTRY_ID: entry.entry_id,
NODE_ID: node.node_id,
@ -385,12 +405,30 @@ async def test_add_node(
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview failed"
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.controller.Controller.async_begin_inclusion",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{ID: 4, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id}
{ID: 5, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
@ -419,12 +457,48 @@ async def test_cancel_inclusion_exclusion(hass, integration, client, hass_ws_cli
msg = await ws_client.receive_json()
assert msg["success"]
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.controller.Controller.async_stop_inclusion",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 6,
TYPE: "zwave_js/stop_inclusion",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.controller.Controller.async_stop_exclusion",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 7,
TYPE: "zwave_js/stop_exclusion",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{ID: 6, TYPE: "zwave_js/stop_inclusion", ENTRY_ID: entry.entry_id}
{ID: 8, TYPE: "zwave_js/stop_inclusion", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
@ -432,7 +506,7 @@ async def test_cancel_inclusion_exclusion(hass, integration, client, hass_ws_cli
assert msg["error"]["code"] == ERR_NOT_LOADED
await ws_client.send_json(
{ID: 7, TYPE: "zwave_js/stop_exclusion", ENTRY_ID: entry.entry_id}
{ID: 9, TYPE: "zwave_js/stop_exclusion", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
@ -494,12 +568,30 @@ async def test_remove_node(
)
assert device is None
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.controller.Controller.async_begin_exclusion",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/remove_node",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{ID: 4, TYPE: "zwave_js/remove_node", ENTRY_ID: entry.entry_id}
{ID: 5, TYPE: "zwave_js/remove_node", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
@ -641,13 +733,32 @@ async def test_replace_failed_node(
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview failed"
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.controller.Controller.async_replace_failed_node",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/replace_failed_node",
ENTRY_ID: entry.entry_id,
NODE_ID: 67,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 4,
ID: 5,
TYPE: "zwave_js/replace_failed_node",
ENTRY_ID: entry.entry_id,
NODE_ID: 67,
@ -705,13 +816,32 @@ async def test_remove_failed_node(
)
assert device is None
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.controller.Controller.async_remove_failed_node",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/remove_failed_node",
ENTRY_ID: entry.entry_id,
NODE_ID: 67,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 4,
ID: 5,
TYPE: "zwave_js/remove_failed_node",
ENTRY_ID: entry.entry_id,
NODE_ID: 67,
@ -747,13 +877,31 @@ async def test_begin_healing_network(
assert msg["success"]
assert msg["result"]
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.controller.Controller.async_begin_healing_network",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/begin_healing_network",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 4,
ID: 5,
TYPE: "zwave_js/begin_healing_network",
ENTRY_ID: entry.entry_id,
}
@ -837,13 +985,31 @@ async def test_stop_healing_network(
assert msg["success"]
assert msg["result"]
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.controller.Controller.async_stop_healing_network",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/stop_healing_network",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 4,
ID: 5,
TYPE: "zwave_js/stop_healing_network",
ENTRY_ID: entry.entry_id,
}
@ -879,13 +1045,32 @@ async def test_heal_node(
assert msg["success"]
assert msg["result"]
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.controller.Controller.async_heal_node",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/heal_node",
ENTRY_ID: entry.entry_id,
NODE_ID: 67,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 4,
ID: 5,
TYPE: "zwave_js/heal_node",
ENTRY_ID: entry.entry_id,
NODE_ID: 67,
@ -978,13 +1163,32 @@ async def test_refresh_node_info(
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.node.Node.async_refresh_info",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/refresh_node_info",
ENTRY_ID: entry.entry_id,
NODE_ID: 52,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 3,
ID: 4,
TYPE: "zwave_js/refresh_node_info",
ENTRY_ID: entry.entry_id,
NODE_ID: 52,
@ -1048,6 +1252,42 @@ async def test_refresh_node_values(
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.node.Node.async_refresh_values",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/refresh_node_values",
ENTRY_ID: entry.entry_id,
NODE_ID: 52,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 5,
TYPE: "zwave_js/refresh_node_values",
ENTRY_ID: entry.entry_id,
NODE_ID: 52,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_refresh_node_cc_values(
hass, client, multisensor_6, integration, hass_ws_client
@ -1105,13 +1345,33 @@ async def test_refresh_node_cc_values(
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.node.Node.async_refresh_cc_values",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/refresh_node_cc_values",
ENTRY_ID: entry.entry_id,
NODE_ID: 52,
COMMAND_CLASS_ID: 112,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 4,
ID: 5,
TYPE: "zwave_js/refresh_node_cc_values",
ENTRY_ID: entry.entry_id,
NODE_ID: 52,
@ -1307,13 +1567,35 @@ async def test_set_config_parameter(
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test FailedZWaveCommand is caught
with patch(
"homeassistant.components.zwave_js.api.async_set_config_parameter",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 7,
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 not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 7,
ID: 8,
TYPE: "zwave_js/set_config_parameter",
ENTRY_ID: entry.entry_id,
NODE_ID: 52,
@ -1625,12 +1907,30 @@ async def test_subscribe_log_updates(hass, integration, client, hass_ws_client):
},
}
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.driver.Driver.async_start_listening_logs",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/subscribe_log_updates",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{ID: 2, TYPE: "zwave_js/subscribe_log_updates", ENTRY_ID: entry.entry_id}
{ID: 3, TYPE: "zwave_js/subscribe_log_updates", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
@ -1757,13 +2057,32 @@ async def test_update_log_config(hass, client, integration, hass_ws_client):
and "must be provided if logging to file" in msg["error"]["message"]
)
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.driver.Driver.async_update_log_config",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 7,
TYPE: "zwave_js/update_log_config",
ENTRY_ID: entry.entry_id,
CONFIG: {LEVEL: "Error"},
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 7,
ID: 8,
TYPE: "zwave_js/update_log_config",
ENTRY_ID: entry.entry_id,
CONFIG: {LEVEL: "Error"},
@ -1884,13 +2203,50 @@ async def test_data_collection(hass, client, integration, hass_ws_client):
client.async_send_command.reset_mock()
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.driver.Driver.async_is_statistics_enabled",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/data_collection_status",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.driver.Driver.async_enable_statistics",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 5,
TYPE: "zwave_js/update_data_collection_preference",
ENTRY_ID: entry.entry_id,
OPTED_IN: True,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 4,
ID: 6,
TYPE: "zwave_js/data_collection_status",
ENTRY_ID: entry.entry_id,
}
@ -1902,7 +2258,7 @@ async def test_data_collection(hass, client, integration, hass_ws_client):
await ws_client.send_json(
{
ID: 5,
ID: 7,
TYPE: "zwave_js/update_data_collection_preference",
ENTRY_ID: entry.entry_id,
OPTED_IN: True,
@ -1938,6 +2294,42 @@ async def test_abort_firmware_update(
assert args["command"] == "node.abort_firmware_update"
assert args["nodeId"] == multisensor_6.node_id
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.node.Node.async_abort_firmware_update",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/abort_firmware_update",
ENTRY_ID: entry.entry_id,
NODE_ID: multisensor_6.node_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/abort_firmware_update",
ENTRY_ID: entry.entry_id,
NODE_ID: multisensor_6.node_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_abort_firmware_update_failures(
hass, integration, multisensor_6, client, hass_ws_client
@ -2128,13 +2520,31 @@ async def test_check_for_config_updates(hass, client, integration, hass_ws_clien
assert config_update["update_available"]
assert config_update["new_version"] == "test"
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.driver.Driver.async_check_for_config_updates",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/check_for_config_updates",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 2,
ID: 3,
TYPE: "zwave_js/check_for_config_updates",
ENTRY_ID: entry.entry_id,
}
@ -2146,7 +2556,7 @@ async def test_check_for_config_updates(hass, client, integration, hass_ws_clien
await ws_client.send_json(
{
ID: 3,
ID: 4,
TYPE: "zwave_js/check_for_config_updates",
ENTRY_ID: "INVALID",
}
@ -2175,13 +2585,31 @@ async def test_install_config_update(hass, client, integration, hass_ws_client):
assert msg["result"]
assert msg["success"]
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.driver.Driver.async_install_config_update",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/install_config_update",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "Z-Wave error 1: error message"
# Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
await ws_client.send_json(
{
ID: 2,
ID: 3,
TYPE: "zwave_js/install_config_update",
ENTRY_ID: entry.entry_id,
}
@ -2193,7 +2621,7 @@ async def test_install_config_update(hass, client, integration, hass_ws_client):
await ws_client.send_json(
{
ID: 3,
ID: 4,
TYPE: "zwave_js/install_config_update",
ENTRY_ID: "INVALID",
}