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 import dataclasses
from functools import partial, wraps from functools import partial, wraps
import json import json
from typing import Callable from typing import Any, Callable
from aiohttp import hdrs, web, web_exceptions, web_request from aiohttp import hdrs, web, web_exceptions, web_request
import voluptuous as vol 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.const import CommandClass, LogLevel
from zwave_js_server.exceptions import ( from zwave_js_server.exceptions import (
BaseZwaveJSServerError, BaseZwaveJSServerError,
FailedCommand,
InvalidNewValue, InvalidNewValue,
NotFoundError, NotFoundError,
SetValueFailed, SetValueFailed,
@ -47,6 +48,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import ( from .const import (
CONF_DATA_COLLECTION_OPTED_IN, CONF_DATA_COLLECTION_OPTED_IN,
DATA_CLIENT, DATA_CLIENT,
DATA_UNSUBSCRIBE,
DOMAIN, DOMAIN,
EVENT_DEVICE_ADDED_TO_REGISTRY, EVENT_DEVICE_ADDED_TO_REGISTRY,
) )
@ -134,6 +136,30 @@ def async_get_node(orig_func: Callable) -> Callable:
return async_get_node_func 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 @callback
def async_register_api(hass: HomeAssistant) -> None: def async_register_api(hass: HomeAssistant) -> None:
"""Register all of our api endpoints.""" """Register all of our api endpoints."""
@ -318,6 +344,7 @@ async def websocket_node_metadata(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_node @async_get_node
async def websocket_ping_node( async def websocket_ping_node(
hass: HomeAssistant, hass: HomeAssistant,
@ -342,6 +369,7 @@ async def websocket_ping_node(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_add_node( async def websocket_add_node(
hass: HomeAssistant, hass: HomeAssistant,
@ -410,7 +438,7 @@ async def websocket_add_node(
) )
connection.subscriptions[msg["id"]] = async_cleanup connection.subscriptions[msg["id"]] = async_cleanup
unsubs = [ msg[DATA_UNSUBSCRIBE] = unsubs = [
controller.on("inclusion started", forward_event), controller.on("inclusion started", forward_event),
controller.on("inclusion failed", forward_event), controller.on("inclusion failed", forward_event),
controller.on("inclusion stopped", forward_event), controller.on("inclusion stopped", forward_event),
@ -435,6 +463,7 @@ async def websocket_add_node(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_stop_inclusion( async def websocket_stop_inclusion(
hass: HomeAssistant, hass: HomeAssistant,
@ -460,6 +489,7 @@ async def websocket_stop_inclusion(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_stop_exclusion( async def websocket_stop_exclusion(
hass: HomeAssistant, hass: HomeAssistant,
@ -485,6 +515,7 @@ async def websocket_stop_exclusion(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_remove_node( async def websocket_remove_node(
hass: HomeAssistant, hass: HomeAssistant,
@ -522,7 +553,7 @@ async def websocket_remove_node(
) )
connection.subscriptions[msg["id"]] = async_cleanup connection.subscriptions[msg["id"]] = async_cleanup
unsubs = [ msg[DATA_UNSUBSCRIBE] = unsubs = [
controller.on("exclusion started", forward_event), controller.on("exclusion started", forward_event),
controller.on("exclusion failed", forward_event), controller.on("exclusion failed", forward_event),
controller.on("exclusion stopped", forward_event), controller.on("exclusion stopped", forward_event),
@ -546,6 +577,7 @@ async def websocket_remove_node(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_replace_failed_node( async def websocket_replace_failed_node(
hass: HomeAssistant, hass: HomeAssistant,
@ -628,7 +660,7 @@ async def websocket_replace_failed_node(
) )
connection.subscriptions[msg["id"]] = async_cleanup connection.subscriptions[msg["id"]] = async_cleanup
unsubs = [ msg[DATA_UNSUBSCRIBE] = unsubs = [
controller.on("inclusion started", forward_event), controller.on("inclusion started", forward_event),
controller.on("inclusion failed", forward_event), controller.on("inclusion failed", forward_event),
controller.on("inclusion stopped", forward_event), controller.on("inclusion stopped", forward_event),
@ -655,6 +687,7 @@ async def websocket_replace_failed_node(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_remove_failed_node( async def websocket_remove_failed_node(
hass: HomeAssistant, hass: HomeAssistant,
@ -670,7 +703,8 @@ async def websocket_remove_failed_node(
@callback @callback
def async_cleanup() -> None: def async_cleanup() -> None:
"""Remove signal listeners.""" """Remove signal listeners."""
unsub() for unsub in unsubs:
unsub()
@callback @callback
def node_removed(event: dict) -> None: def node_removed(event: dict) -> None:
@ -686,7 +720,7 @@ async def websocket_remove_failed_node(
) )
connection.subscriptions[msg["id"]] = async_cleanup 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) result = await controller.async_remove_failed_node(node_id)
connection.send_result( connection.send_result(
@ -703,6 +737,7 @@ async def websocket_remove_failed_node(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_begin_healing_network( async def websocket_begin_healing_network(
hass: HomeAssistant, hass: HomeAssistant,
@ -755,7 +790,7 @@ async def websocket_subscribe_heal_network_progress(
) )
connection.subscriptions[msg["id"]] = async_cleanup connection.subscriptions[msg["id"]] = async_cleanup
unsubs = [ msg[DATA_UNSUBSCRIBE] = unsubs = [
controller.on("heal network progress", partial(forward_event, "progress")), controller.on("heal network progress", partial(forward_event, "progress")),
controller.on("heal network done", partial(forward_event, "result")), controller.on("heal network done", partial(forward_event, "result")),
] ]
@ -771,6 +806,7 @@ async def websocket_subscribe_heal_network_progress(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_stop_healing_network( async def websocket_stop_healing_network(
hass: HomeAssistant, hass: HomeAssistant,
@ -797,6 +833,7 @@ async def websocket_stop_healing_network(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_heal_node( async def websocket_heal_node(
hass: HomeAssistant, hass: HomeAssistant,
@ -824,6 +861,7 @@ async def websocket_heal_node(
}, },
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_node @async_get_node
async def websocket_refresh_node_info( async def websocket_refresh_node_info(
hass: HomeAssistant, hass: HomeAssistant,
@ -854,7 +892,7 @@ async def websocket_refresh_node_info(
) )
connection.subscriptions[msg["id"]] = async_cleanup connection.subscriptions[msg["id"]] = async_cleanup
unsubs = [ msg[DATA_UNSUBSCRIBE] = unsubs = [
node.on("interview started", forward_event), node.on("interview started", forward_event),
node.on("interview completed", forward_event), node.on("interview completed", forward_event),
node.on("interview stage completed", forward_stage), node.on("interview stage completed", forward_stage),
@ -874,6 +912,7 @@ async def websocket_refresh_node_info(
}, },
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_node @async_get_node
async def websocket_refresh_node_values( async def websocket_refresh_node_values(
hass: HomeAssistant, hass: HomeAssistant,
@ -896,6 +935,7 @@ async def websocket_refresh_node_values(
}, },
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_node @async_get_node
async def websocket_refresh_node_cc_values( async def websocket_refresh_node_cc_values(
hass: HomeAssistant, hass: HomeAssistant,
@ -930,6 +970,7 @@ async def websocket_refresh_node_cc_values(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_node @async_get_node
async def websocket_set_config_parameter( async def websocket_set_config_parameter(
hass: HomeAssistant, hass: HomeAssistant,
@ -1027,6 +1068,7 @@ def filename_is_present_if_logging_to_file(obj: dict) -> dict:
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_subscribe_log_updates( async def websocket_subscribe_log_updates(
hass: HomeAssistant, hass: HomeAssistant,
@ -1076,7 +1118,7 @@ async def websocket_subscribe_log_updates(
) )
) )
unsubs = [ msg[DATA_UNSUBSCRIBE] = unsubs = [
driver.on("logging", log_messages), driver.on("logging", log_messages),
driver.on("log config updated", log_config_updates), driver.on("log config updated", log_config_updates),
] ]
@ -1114,6 +1156,7 @@ async def websocket_subscribe_log_updates(
}, },
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_update_log_config( async def websocket_update_log_config(
hass: HomeAssistant, hass: HomeAssistant,
@ -1161,6 +1204,7 @@ async def websocket_get_log_config(
}, },
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_update_data_collection_preference( async def websocket_update_data_collection_preference(
hass: HomeAssistant, hass: HomeAssistant,
@ -1191,6 +1235,7 @@ async def websocket_update_data_collection_preference(
}, },
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_data_collection_status( async def websocket_data_collection_status(
hass: HomeAssistant, hass: HomeAssistant,
@ -1273,6 +1318,7 @@ async def websocket_version_info(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_node @async_get_node
async def websocket_abort_firmware_update( async def websocket_abort_firmware_update(
hass: HomeAssistant, 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 progress", forward_progress),
node.on("firmware update finished", forward_finished), node.on("firmware update finished", forward_finished),
] ]
@ -1400,6 +1446,7 @@ class FirmwareUploadView(HomeAssistantView):
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_check_for_config_updates( async def websocket_check_for_config_updates(
hass: HomeAssistant, hass: HomeAssistant,
@ -1427,6 +1474,7 @@ async def websocket_check_for_config_updates(
} }
) )
@websocket_api.async_response @websocket_api.async_response
@async_handle_failed_command
@async_get_entry @async_get_entry
async def websocket_install_config_update( async def websocket_install_config_update(
hass: HomeAssistant, 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.event import Event
from zwave_js_server.exceptions import ( from zwave_js_server.exceptions import (
FailedCommand, FailedCommand,
FailedZWaveCommand,
InvalidNewValue, InvalidNewValue,
NotFoundError, NotFoundError,
SetValueFailed, SetValueFailed,
@ -280,13 +281,32 @@ async def test_ping_node(
assert not msg["success"] assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 5, ID: 6,
TYPE: "zwave_js/ping_node", TYPE: "zwave_js/ping_node",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
NODE_ID: node.node_id, NODE_ID: node.node_id,
@ -385,12 +405,30 @@ async def test_add_node(
msg = await ws_client.receive_json() msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview failed" 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( 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() 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() msg = await ws_client.receive_json()
assert msg["success"] 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( 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() 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 assert msg["error"]["code"] == ERR_NOT_LOADED
await ws_client.send_json( 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() msg = await ws_client.receive_json()
@ -494,12 +568,30 @@ async def test_remove_node(
) )
assert device is None 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( 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() msg = await ws_client.receive_json()
@ -641,13 +733,32 @@ async def test_replace_failed_node(
msg = await ws_client.receive_json() msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview failed" 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 4, ID: 5,
TYPE: "zwave_js/replace_failed_node", TYPE: "zwave_js/replace_failed_node",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
NODE_ID: 67, NODE_ID: 67,
@ -705,13 +816,32 @@ async def test_remove_failed_node(
) )
assert device is None 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 4, ID: 5,
TYPE: "zwave_js/remove_failed_node", TYPE: "zwave_js/remove_failed_node",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
NODE_ID: 67, NODE_ID: 67,
@ -747,13 +877,31 @@ async def test_begin_healing_network(
assert msg["success"] assert msg["success"]
assert msg["result"] 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 4, ID: 5,
TYPE: "zwave_js/begin_healing_network", TYPE: "zwave_js/begin_healing_network",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
} }
@ -837,13 +985,31 @@ async def test_stop_healing_network(
assert msg["success"] assert msg["success"]
assert msg["result"] 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 4, ID: 5,
TYPE: "zwave_js/stop_healing_network", TYPE: "zwave_js/stop_healing_network",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
} }
@ -879,13 +1045,32 @@ async def test_heal_node(
assert msg["success"] assert msg["success"]
assert msg["result"] 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 4, ID: 5,
TYPE: "zwave_js/heal_node", TYPE: "zwave_js/heal_node",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
NODE_ID: 67, NODE_ID: 67,
@ -978,13 +1163,32 @@ async def test_refresh_node_info(
assert not msg["success"] assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 3, ID: 4,
TYPE: "zwave_js/refresh_node_info", TYPE: "zwave_js/refresh_node_info",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
NODE_ID: 52, NODE_ID: 52,
@ -1048,6 +1252,42 @@ async def test_refresh_node_values(
assert not msg["success"] assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND 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( async def test_refresh_node_cc_values(
hass, client, multisensor_6, integration, hass_ws_client hass, client, multisensor_6, integration, hass_ws_client
@ -1105,13 +1345,33 @@ async def test_refresh_node_cc_values(
assert not msg["success"] assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 4, ID: 5,
TYPE: "zwave_js/refresh_node_cc_values", TYPE: "zwave_js/refresh_node_cc_values",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
NODE_ID: 52, NODE_ID: 52,
@ -1307,13 +1567,35 @@ async def test_set_config_parameter(
assert not msg["success"] assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 7, ID: 8,
TYPE: "zwave_js/set_config_parameter", TYPE: "zwave_js/set_config_parameter",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
NODE_ID: 52, 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( 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() 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"] 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 7, ID: 8,
TYPE: "zwave_js/update_log_config", TYPE: "zwave_js/update_log_config",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
CONFIG: {LEVEL: "Error"}, CONFIG: {LEVEL: "Error"},
@ -1884,13 +2203,50 @@ async def test_data_collection(hass, client, integration, hass_ws_client):
client.async_send_command.reset_mock() 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 4, ID: 6,
TYPE: "zwave_js/data_collection_status", TYPE: "zwave_js/data_collection_status",
ENTRY_ID: entry.entry_id, 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( await ws_client.send_json(
{ {
ID: 5, ID: 7,
TYPE: "zwave_js/update_data_collection_preference", TYPE: "zwave_js/update_data_collection_preference",
ENTRY_ID: entry.entry_id, ENTRY_ID: entry.entry_id,
OPTED_IN: True, OPTED_IN: True,
@ -1938,6 +2294,42 @@ async def test_abort_firmware_update(
assert args["command"] == "node.abort_firmware_update" assert args["command"] == "node.abort_firmware_update"
assert args["nodeId"] == multisensor_6.node_id 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( async def test_abort_firmware_update_failures(
hass, integration, multisensor_6, client, hass_ws_client 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["update_available"]
assert config_update["new_version"] == "test" 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 2, ID: 3,
TYPE: "zwave_js/check_for_config_updates", TYPE: "zwave_js/check_for_config_updates",
ENTRY_ID: entry.entry_id, 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( await ws_client.send_json(
{ {
ID: 3, ID: 4,
TYPE: "zwave_js/check_for_config_updates", TYPE: "zwave_js/check_for_config_updates",
ENTRY_ID: "INVALID", ENTRY_ID: "INVALID",
} }
@ -2175,13 +2585,31 @@ async def test_install_config_update(hass, client, integration, hass_ws_client):
assert msg["result"] assert msg["result"]
assert msg["success"] 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 # Test sending command with not loaded entry fails
await hass.config_entries.async_unload(entry.entry_id) await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
await ws_client.send_json( await ws_client.send_json(
{ {
ID: 2, ID: 3,
TYPE: "zwave_js/install_config_update", TYPE: "zwave_js/install_config_update",
ENTRY_ID: entry.entry_id, 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( await ws_client.send_json(
{ {
ID: 3, ID: 4,
TYPE: "zwave_js/install_config_update", TYPE: "zwave_js/install_config_update",
ENTRY_ID: "INVALID", ENTRY_ID: "INVALID",
} }