core/tests/components/zwave_js/test_api.py

5198 lines
152 KiB
Python

"""Test the Z-Wave JS Websocket API."""
from copy import deepcopy
from http import HTTPStatus
from io import BytesIO
import json
from typing import Any
from unittest.mock import MagicMock, PropertyMock, patch
import pytest
from zwave_js_server.const import (
ExclusionStrategy,
InclusionState,
InclusionStrategy,
LogLevel,
Protocols,
ProvisioningEntryStatus,
QRCodeVersion,
SecurityClass,
ZwaveFeature,
)
from zwave_js_server.event import Event
from zwave_js_server.exceptions import (
FailedCommand,
FailedZWaveCommand,
InvalidNewValue,
NotFoundError,
SetValueFailed,
)
from zwave_js_server.model.controller import (
ProvisioningEntry,
QRProvisioningInformation,
)
from zwave_js_server.model.controller.firmware import ControllerFirmwareUpdateData
from zwave_js_server.model.node import Node
from zwave_js_server.model.node.firmware import NodeFirmwareUpdateData
from zwave_js_server.model.value import ConfigurationValue, get_value_id_str
from homeassistant.components.websocket_api import ERR_INVALID_FORMAT, ERR_NOT_FOUND
from homeassistant.components.zwave_js.api import (
APPLICATION_VERSION,
CLIENT_SIDE_AUTH,
COMMAND_CLASS_ID,
CONFIG,
DEVICE_ID,
DSK,
ENABLED,
ENDPOINT,
ENTRY_ID,
ERR_NOT_LOADED,
FEATURE,
FILENAME,
FORCE_CONSOLE,
GENERIC_DEVICE_CLASS,
ID,
INCLUSION_STRATEGY,
INSTALLER_ICON_TYPE,
LEVEL,
LOG_TO_FILE,
MANUFACTURER_ID,
MAX_INCLUSION_REQUEST_INTERVAL,
NODE_ID,
OPTED_IN,
PIN,
PLANNED_PROVISIONING_ENTRY,
PRODUCT_ID,
PRODUCT_TYPE,
PROPERTY,
PROPERTY_KEY,
QR_CODE_STRING,
QR_PROVISIONING_INFORMATION,
REQUESTED_SECURITY_CLASSES,
SECURITY_CLASSES,
SPECIFIC_DEVICE_CLASS,
STATUS,
STRATEGY,
SUPPORTED_PROTOCOLS,
TYPE,
UUID,
VALUE,
VALUE_FORMAT,
VALUE_SIZE,
VERSION,
)
from homeassistant.components.zwave_js.const import (
ATTR_COMMAND_CLASS,
ATTR_ENDPOINT,
ATTR_METHOD_NAME,
ATTR_PARAMETERS,
ATTR_WAIT_FOR_RESULT,
CONF_DATA_COLLECTION_OPTED_IN,
CONF_INSTALLER_MODE,
DOMAIN,
)
from homeassistant.components.zwave_js.helpers import get_device_id
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry, MockUser
from tests.typing import ClientSessionGenerator, WebSocketGenerator
CONTROLLER_PATCH_PREFIX = "zwave_js_server.model.controller.Controller"
def get_device(hass: HomeAssistant, node):
"""Get device ID for a node."""
dev_reg = dr.async_get(hass)
device_id = get_device_id(node.client.driver, node)
return dev_reg.async_get_device(identifiers={device_id})
async def test_no_driver(
hass: HomeAssistant,
client,
multisensor_6,
controller_state,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test driver missing results in error."""
entry = integration
ws_client = await hass_ws_client(hass)
client.driver = None
# Try API call with entry ID
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/network_status",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
async def test_network_status(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
multisensor_6,
controller_state,
client,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the network status websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.server_logging_enabled = False
# Try API call with entry ID
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_get_state",
return_value=controller_state["controller"],
):
await ws_client.send_json(
{
ID: 1,
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"
assert not result["client"]["server_logging_enabled"]
assert result["controller"]["inclusion_state"] == InclusionState.IDLE
# Try API call with device ID
device = device_registry.async_get_device(
identifiers={(DOMAIN, "3245146787-52")},
)
assert device
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_get_state",
return_value=controller_state["controller"],
):
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/network_status",
DEVICE_ID: device.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"
assert result["controller"]["inclusion_state"] == InclusionState.IDLE
# Test sending command with invalid config entry ID fails
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/network_status",
ENTRY_ID: "fake_id",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test sending command with invalid device ID fails
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/network_status",
DEVICE_ID: "fake_id",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test sending command with not loaded entry fails with config entry ID
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/network_status",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
# Test sending command with not loaded entry fails with device ID
await ws_client.send_json(
{
ID: 6,
TYPE: "zwave_js/network_status",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
# Test sending command with no device ID or entry ID fails
await ws_client.send_json(
{
ID: 7,
TYPE: "zwave_js/network_status",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_INVALID_FORMAT
async def test_subscribe_node_status(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
multisensor_6_state,
client,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the subscribe node status websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
node_data = deepcopy(multisensor_6_state) # Copy to allow modification in tests.
node = Node(client, node_data)
node.data["ready"] = False
driver = client.driver
driver.controller.nodes[node.node_id] = node
device = device_registry.async_get_or_create(
config_entry_id=entry.entry_id, identifiers={get_device_id(driver, node)}
)
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/subscribe_node_status",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
new_node_data = deepcopy(multisensor_6_state)
new_node_data["ready"] = True
event = Event(
"ready",
{
"source": "node",
"event": "ready",
"nodeId": node.node_id,
"nodeState": new_node_data,
},
)
node.receive_event(event)
await hass.async_block_till_done()
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "ready"
assert msg["event"]["status"] == 1
assert msg["event"]["ready"]
event = Event(
"wake up",
{
"source": "node",
"event": "wake up",
"nodeId": node.node_id,
},
)
node.receive_event(event)
await hass.async_block_till_done()
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "wake up"
assert msg["event"]["status"] == 2
assert msg["event"]["ready"]
async def test_node_status(
hass: HomeAssistant, multisensor_6, integration, hass_ws_client: WebSocketGenerator
) -> None:
"""Test the node status websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
node = multisensor_6
device = get_device(hass, node)
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/node_status",
DEVICE_ID: device.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
assert result["zwave_plus_version"] == 1
assert result["highest_security_class"] == SecurityClass.S0_LEGACY
assert not result["is_controller_node"]
assert not result["has_firmware_update_cc"]
# Test getting non-existent node fails
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/node_status",
DEVICE_ID: "fake_device",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# 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/node_status",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_node_metadata(
hass: HomeAssistant,
wallmote_central_scene,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the node metadata websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
node = wallmote_central_scene
device = get_device(hass, node)
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/node_metadata",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
result = msg["result"]
assert result[NODE_ID] == 35
assert result["inclusion"] == (
"To add the ZP3111 to the Z-Wave network (inclusion), place the Z-Wave "
"primary controller into inclusion mode. Press the Program Switch of ZP3111 "
"for sending the NIF. After sending NIF, Z-Wave will send the auto inclusion, "
"otherwise, ZP3111 will go to sleep after 20 seconds."
)
assert result["exclusion"] == (
"To remove the ZP3111 from the Z-Wave network (exclusion), place the Z-Wave "
"primary controller into \u201cexclusion\u201d mode, and following its "
"instruction to delete the ZP3111 to the controller. Press the Program Switch "
"of ZP3111 once to be excluded."
)
assert result["reset"] == (
"Remove cover to triggered tamper switch, LED flash once & send out Alarm "
"Report. Press Program Switch 10 times within 10 seconds, ZP3111 will send "
"the \u201cDevice Reset Locally Notification\u201d command and reset to the "
"factory default. (Remark: This is to be used only in the case of primary "
"controller being inoperable or otherwise unavailable.)"
)
assert result["manual"] == (
"https://products.z-wavealliance.org/ProductManual/File?folder=&filename="
"MarketCertificationFiles/2479/ZP3111-5_R2_20170316.pdf"
)
assert not result["wakeup"]
assert (
result["device_database_url"]
== "https://devices.zwave-js.io/?jumpTo=0x0086:0x0002:0x0082:0.0"
)
# Test getting non-existent node fails
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/node_metadata",
DEVICE_ID: "fake_device",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# 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/node_metadata",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_node_alerts(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
wallmote_central_scene,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the node comments websocket command."""
ws_client = await hass_ws_client(hass)
device = device_registry.async_get_device(identifiers={(DOMAIN, "3245146787-35")})
assert device
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/node_alerts",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
result = msg["result"]
assert result["comments"] == [{"level": "info", "text": "test"}]
assert result["is_embedded"]
async def test_add_node(
hass: HomeAssistant,
nortek_thermostat,
nortek_thermostat_added_event,
integration,
client,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the add_node websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {"success": True}
# Test inclusion with no provisioning input
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.DEFAULT.value,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.begin_inclusion",
"options": {"strategy": InclusionStrategy.DEFAULT},
}
event = Event(
type="inclusion started",
data={
"source": "controller",
"event": "inclusion started",
"strategy": 2,
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "inclusion started"
event = Event(
type="node found",
data={
"source": "controller",
"event": "node found",
"node": {
"nodeId": 67,
},
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "node found"
node_details = {
"node_id": 67,
}
assert msg["event"]["node"] == node_details
event = Event(
type="grant security classes",
data={
"source": "controller",
"event": "grant security classes",
"requested": {"securityClasses": [0, 1, 2, 7], "clientSideAuth": False},
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "grant security classes"
assert msg["event"]["requested_grant"] == {
"securityClasses": [0, 1, 2, 7],
"clientSideAuth": False,
}
event = Event(
type="validate dsk and enter pin",
data={
"source": "controller",
"event": "validate dsk and enter pin",
"dsk": "test",
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "validate dsk and enter pin"
assert msg["event"]["dsk"] == "test"
client.driver.receive_event(nortek_thermostat_added_event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "node added"
node_details = {
"node_id": 67,
"status": 0,
"ready": False,
"low_security": False,
"low_security_reason": None,
}
assert msg["event"]["node"] == node_details
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "device registered"
# Check the keys of the device item
assert list(msg["event"]["device"]) == ["name", "id", "manufacturer", "model"]
# Test receiving interview events
event = Event(
type="interview started",
data={"source": "node", "event": "interview started", "nodeId": 67},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview started"
event = Event(
type="interview stage completed",
data={
"source": "node",
"event": "interview stage completed",
"stageName": "NodeInfo",
"nodeId": 67,
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview stage completed"
assert msg["event"]["stage"] == "NodeInfo"
event = Event(
type="interview completed",
data={"source": "node", "event": "interview completed", "nodeId": 67},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview completed"
event = Event(
type="interview failed",
data={
"source": "node",
"event": "interview failed",
"nodeId": 67,
"args": {
"errorMessage": "error",
"isFinal": True,
},
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview failed"
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test S2 planned provisioning entry
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
PLANNED_PROVISIONING_ENTRY: {
DSK: "test",
SECURITY_CLASSES: [0],
},
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.begin_inclusion",
"options": {
"strategy": InclusionStrategy.SECURITY_S2,
"provisioning": ProvisioningEntry(
"test", [SecurityClass.S2_UNAUTHENTICATED]
).to_dict(),
},
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test S2 QR provisioning information
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
QR_PROVISIONING_INFORMATION: {
VERSION: 0,
SECURITY_CLASSES: [0],
DSK: "test",
GENERIC_DEVICE_CLASS: 1,
SPECIFIC_DEVICE_CLASS: 1,
INSTALLER_ICON_TYPE: 1,
MANUFACTURER_ID: 1,
PRODUCT_TYPE: 1,
PRODUCT_ID: 1,
APPLICATION_VERSION: "test",
},
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.begin_inclusion",
"options": {
"strategy": InclusionStrategy.SECURITY_S2,
"provisioning": QRProvisioningInformation(
version=QRCodeVersion.S2,
security_classes=[SecurityClass.S2_UNAUTHENTICATED],
dsk="test",
generic_device_class=1,
specific_device_class=1,
installer_icon_type=1,
manufacturer_id=1,
product_type=1,
product_id=1,
application_version="test",
max_inclusion_request_interval=None,
uuid=None,
supported_protocols=None,
).to_dict(),
},
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test S2 QR provisioning information
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
QR_PROVISIONING_INFORMATION: {
VERSION: 0,
SECURITY_CLASSES: [0],
DSK: "test",
GENERIC_DEVICE_CLASS: 1,
SPECIFIC_DEVICE_CLASS: 1,
INSTALLER_ICON_TYPE: 1,
MANUFACTURER_ID: 1,
PRODUCT_TYPE: 1,
PRODUCT_ID: 1,
APPLICATION_VERSION: "test",
STATUS: 1,
REQUESTED_SECURITY_CLASSES: [0],
},
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.begin_inclusion",
"options": {
"strategy": InclusionStrategy.SECURITY_S2,
"provisioning": QRProvisioningInformation(
version=QRCodeVersion.S2,
security_classes=[SecurityClass.S2_UNAUTHENTICATED],
dsk="test",
generic_device_class=1,
specific_device_class=1,
installer_icon_type=1,
manufacturer_id=1,
product_type=1,
product_id=1,
application_version="test",
max_inclusion_request_interval=None,
uuid=None,
supported_protocols=None,
status=ProvisioningEntryStatus.INACTIVE,
requested_security_classes=[SecurityClass.S2_UNAUTHENTICATED],
).to_dict(),
},
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test S2 QR code string
await ws_client.send_json(
{
ID: 5,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.begin_inclusion",
"options": {
"strategy": InclusionStrategy.SECURITY_S2,
"provisioning": "90testtesttesttesttesttesttesttesttesttesttesttesttest",
},
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test S2 DSK string string
await ws_client.send_json(
{
ID: 6,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
DSK: "test_dsk",
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.begin_inclusion",
"options": {
"strategy": InclusionStrategy.SECURITY_S2,
"dsk": "test_dsk",
},
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test Smart Start QR provisioning information with S2 inclusion strategy fails
await ws_client.send_json(
{
ID: 7,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
QR_PROVISIONING_INFORMATION: {
VERSION: 1,
SECURITY_CLASSES: [0],
DSK: "test",
GENERIC_DEVICE_CLASS: 1,
SPECIFIC_DEVICE_CLASS: 1,
INSTALLER_ICON_TYPE: 1,
MANUFACTURER_ID: 1,
PRODUCT_TYPE: 1,
PRODUCT_ID: 1,
APPLICATION_VERSION: "test",
},
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert len(client.async_send_command.call_args_list) == 0
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test QR provisioning information with S0 inclusion strategy fails
await ws_client.send_json(
{
ID: 8,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S0,
QR_PROVISIONING_INFORMATION: {
VERSION: 1,
SECURITY_CLASSES: [0],
DSK: "test",
GENERIC_DEVICE_CLASS: 1,
SPECIFIC_DEVICE_CLASS: 1,
INSTALLER_ICON_TYPE: 1,
MANUFACTURER_ID: 1,
PRODUCT_TYPE: 1,
PRODUCT_ID: 1,
APPLICATION_VERSION: "test",
},
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert len(client.async_send_command.call_args_list) == 0
client.async_send_command.reset_mock()
client.async_send_command.return_value = {" success": True}
# Test ValueError is caught as failure
await ws_client.send_json(
{
ID: 9,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.DEFAULT.value,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert len(client.async_send_command.call_args_list) == 0
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_begin_inclusion",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 10,
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"] == "zwave_error: Z-Wave error 1 - error message"
# Test inclusion already in progress
client.async_send_command.reset_mock()
type(client.driver.controller).inclusion_state = PropertyMock(
return_value=InclusionState.INCLUDING
)
# Create a node that's not ready
node_data = deepcopy(nortek_thermostat.data) # Copy to allow modification in tests.
node_data["ready"] = False
node_data["values"] = {}
node_data["endpoints"] = {}
node = Node(client, node_data)
client.driver.controller.nodes[node.node_id] = node
await ws_client.send_json(
{
ID: 11,
TYPE: "zwave_js/add_node",
ENTRY_ID: entry.entry_id,
INCLUSION_STRATEGY: InclusionStrategy.DEFAULT.value,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
# Verify no command was sent since inclusion is already in progress
assert len(client.async_send_command.call_args_list) == 0
# Verify we got a node added event
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "node added"
assert msg["event"]["node"]["node_id"] == node.node_id
# 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: 12, TYPE: "zwave_js/add_node", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_grant_security_classes(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test the grant_security_classes websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/grant_security_classes",
ENTRY_ID: entry.entry_id,
SECURITY_CLASSES: [SecurityClass.S2_UNAUTHENTICATED],
CLIENT_SIDE_AUTH: False,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.grant_security_classes",
"inclusionGrant": {"securityClasses": [0], "clientSideAuth": False},
}
# 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/grant_security_classes",
ENTRY_ID: entry.entry_id,
SECURITY_CLASSES: [SecurityClass.S2_UNAUTHENTICATED],
CLIENT_SIDE_AUTH: False,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_validate_dsk_and_enter_pin(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test the validate_dsk_and_enter_pin websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/validate_dsk_and_enter_pin",
ENTRY_ID: entry.entry_id,
PIN: "test",
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.validate_dsk_and_enter_pin",
"pin": "test",
}
# 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/validate_dsk_and_enter_pin",
ENTRY_ID: entry.entry_id,
PIN: "test",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_provision_smart_start_node(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test provision_smart_start_node websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {"success": True}
# Test provisioning entry
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/provision_smart_start_node",
ENTRY_ID: entry.entry_id,
PLANNED_PROVISIONING_ENTRY: {
DSK: "test",
SECURITY_CLASSES: [0],
},
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.provision_smart_start_node",
"entry": ProvisioningEntry(
"test", [SecurityClass.S2_UNAUTHENTICATED]
).to_dict(),
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test QR provisioning information
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/provision_smart_start_node",
ENTRY_ID: entry.entry_id,
QR_PROVISIONING_INFORMATION: {
VERSION: 1,
SECURITY_CLASSES: [0],
DSK: "test",
GENERIC_DEVICE_CLASS: 1,
SPECIFIC_DEVICE_CLASS: 1,
INSTALLER_ICON_TYPE: 1,
MANUFACTURER_ID: 1,
PRODUCT_TYPE: 1,
PRODUCT_ID: 1,
APPLICATION_VERSION: "test",
"name": "test",
},
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.provision_smart_start_node",
"entry": QRProvisioningInformation(
version=QRCodeVersion.SMART_START,
security_classes=[SecurityClass.S2_UNAUTHENTICATED],
dsk="test",
generic_device_class=1,
specific_device_class=1,
installer_icon_type=1,
manufacturer_id=1,
product_type=1,
product_id=1,
application_version="test",
max_inclusion_request_interval=None,
uuid=None,
supported_protocols=None,
additional_properties={"name": "test"},
).to_dict(),
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test QR code string
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/provision_smart_start_node",
ENTRY_ID: entry.entry_id,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.provision_smart_start_node",
"entry": "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test QR provisioning information with S2 version throws error
await ws_client.send_json(
{
ID: 5,
TYPE: "zwave_js/provision_smart_start_node",
ENTRY_ID: entry.entry_id,
QR_PROVISIONING_INFORMATION: {
VERSION: 0,
SECURITY_CLASSES: [0],
DSK: "test",
GENERIC_DEVICE_CLASS: 1,
SPECIFIC_DEVICE_CLASS: 1,
INSTALLER_ICON_TYPE: 1,
MANUFACTURER_ID: 1,
PRODUCT_TYPE: 1,
PRODUCT_ID: 1,
APPLICATION_VERSION: "test",
},
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
assert len(client.async_send_command.call_args_list) == 0
# Test no provisioning parameter provided causes failure
await ws_client.send_json(
{
ID: 6,
TYPE: "zwave_js/provision_smart_start_node",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_provision_smart_start_node",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 7,
TYPE: "zwave_js/provision_smart_start_node",
ENTRY_ID: entry.entry_id,
QR_CODE_STRING: (
"90testtesttesttesttesttesttesttesttesttesttesttesttest"
),
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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: 8,
TYPE: "zwave_js/provision_smart_start_node",
ENTRY_ID: entry.entry_id,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_unprovision_smart_start_node(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test unprovision_smart_start_node websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {}
# Test node ID as input
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/unprovision_smart_start_node",
ENTRY_ID: entry.entry_id,
NODE_ID: 1,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.unprovision_smart_start_node",
"dskOrNodeId": 1,
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {}
# Test DSK as input
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/unprovision_smart_start_node",
ENTRY_ID: entry.entry_id,
DSK: "test",
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.unprovision_smart_start_node",
"dskOrNodeId": "test",
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {}
# Test not including DSK or node ID as input fails
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/unprovision_smart_start_node",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert len(client.async_send_command.call_args_list) == 0
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_unprovision_smart_start_node",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 6,
TYPE: "zwave_js/unprovision_smart_start_node",
ENTRY_ID: entry.entry_id,
DSK: "test",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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,
TYPE: "zwave_js/unprovision_smart_start_node",
ENTRY_ID: entry.entry_id,
DSK: "test",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_get_provisioning_entries(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test get_provisioning_entries websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {
"entries": [{"dsk": "test", "securityClasses": [0], "fake": "test"}]
}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/get_provisioning_entries",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] == [
{DSK: "test", SECURITY_CLASSES: [0], STATUS: 0, "fake": "test"}
]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.get_provisioning_entries",
}
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_get_provisioning_entries",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 6,
TYPE: "zwave_js/get_provisioning_entries",
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"] == "zwave_error: 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, TYPE: "zwave_js/get_provisioning_entries", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_parse_qr_code_string(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test parse_qr_code_string websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {
"qrProvisioningInformation": {
"version": 0,
"securityClasses": [0],
"dsk": "test",
"genericDeviceClass": 1,
"specificDeviceClass": 1,
"installerIconType": 1,
"manufacturerId": 1,
"productType": 1,
"productId": 1,
"applicationVersion": "test",
"maxInclusionRequestInterval": 1,
"uuid": "test",
"supportedProtocols": [0],
}
}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/parse_qr_code_string",
ENTRY_ID: entry.entry_id,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] == {
VERSION: 0,
SECURITY_CLASSES: [0],
DSK: "test",
GENERIC_DEVICE_CLASS: 1,
SPECIFIC_DEVICE_CLASS: 1,
INSTALLER_ICON_TYPE: 1,
MANUFACTURER_ID: 1,
PRODUCT_TYPE: 1,
PRODUCT_ID: 1,
APPLICATION_VERSION: "test",
MAX_INCLUSION_REQUEST_INTERVAL: 1,
UUID: "test",
SUPPORTED_PROTOCOLS: [Protocols.ZWAVE],
STATUS: 0,
}
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "utils.parse_qr_code_string",
"qr": "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
# Test FailedZWaveCommand is caught
with patch(
"homeassistant.components.zwave_js.api.async_parse_qr_code_string",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 6,
TYPE: "zwave_js/parse_qr_code_string",
ENTRY_ID: entry.entry_id,
QR_CODE_STRING: (
"90testtesttesttesttesttesttesttesttesttesttesttesttest"
),
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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,
TYPE: "zwave_js/parse_qr_code_string",
ENTRY_ID: entry.entry_id,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_try_parse_dsk_from_qr_code_string(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test try_parse_dsk_from_qr_code_string websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {"dsk": "a"}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/try_parse_dsk_from_qr_code_string",
ENTRY_ID: entry.entry_id,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] == "a"
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "utils.try_parse_dsk_from_qr_code_string",
"qr": "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
# Test FailedZWaveCommand is caught
with patch(
"homeassistant.components.zwave_js.api.async_try_parse_dsk_from_qr_code_string",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 6,
TYPE: "zwave_js/try_parse_dsk_from_qr_code_string",
ENTRY_ID: entry.entry_id,
QR_CODE_STRING: (
"90testtesttesttesttesttesttesttesttesttesttesttesttest"
),
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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,
TYPE: "zwave_js/try_parse_dsk_from_qr_code_string",
ENTRY_ID: entry.entry_id,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_supports_feature(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test supports_feature websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {"supported": True}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/supports_feature",
ENTRY_ID: entry.entry_id,
FEATURE: ZwaveFeature.SMART_START,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] == {"supported": True}
async def test_cancel_inclusion_exclusion(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""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"]
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.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"] == "zwave_error: Z-Wave error 1 - error message"
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.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"] == "zwave_error: 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: 8, TYPE: "zwave_js/stop_inclusion", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
await ws_client.send_json(
{ID: 9, TYPE: "zwave_js/stop_exclusion", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_remove_node(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
integration,
client,
hass_ws_client: WebSocketGenerator,
nortek_thermostat,
nortek_thermostat_removed_event,
) -> None:
"""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: 1, TYPE: "zwave_js/remove_node", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.begin_exclusion",
}
event = Event(
type="exclusion started",
data={
"source": "controller",
"event": "exclusion started",
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "exclusion started"
# Create device registry entry for mock node
device = device_registry.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 = device_registry.async_get_device(
identifiers={(DOMAIN, "3245146787-67")},
)
assert device is None
# Test unprovision parameter
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/remove_node",
ENTRY_ID: entry.entry_id,
STRATEGY: ExclusionStrategy.EXCLUDE_ONLY,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.begin_exclusion",
"options": {"strategy": 0},
}
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.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"] == "zwave_error: 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/remove_node", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_replace_failed_node(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
nortek_thermostat,
integration,
client,
hass_ws_client: WebSocketGenerator,
nortek_thermostat_added_event,
nortek_thermostat_removed_event,
) -> None:
"""Test the replace_failed_node websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
# Create device registry entry for mock node
device = device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, "3245146787-67")},
name="Node 67",
)
client.async_send_command.return_value = {"success": True}
# Test replace failed node with no provisioning information
# Order of events we receive for a successful replacement is `inclusion started`,
# `inclusion stopped`, `node removed`, `node added`, then interview stages.
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/replace_failed_node",
DEVICE_ID: device.id,
INCLUSION_STRATEGY: InclusionStrategy.DEFAULT.value,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.replace_failed_node",
"nodeId": nortek_thermostat.node_id,
"options": {"strategy": InclusionStrategy.DEFAULT},
}
client.async_send_command.reset_mock()
event = Event(
type="inclusion started",
data={
"source": "controller",
"event": "inclusion started",
"strategy": 2,
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "inclusion started"
event = Event(
type="node found",
data={
"source": "controller",
"event": "node found",
"node": {
"nodeId": 67,
},
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "node found"
node_details = {
"node_id": 67,
}
assert msg["event"]["node"] == node_details
event = Event(
type="grant security classes",
data={
"source": "controller",
"event": "grant security classes",
"requested": {"securityClasses": [0, 1, 2, 7], "clientSideAuth": False},
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "grant security classes"
assert msg["event"]["requested_grant"] == {
"securityClasses": [0, 1, 2, 7],
"clientSideAuth": False,
}
event = Event(
type="validate dsk and enter pin",
data={
"source": "controller",
"event": "validate dsk and enter pin",
"dsk": "test",
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "validate dsk and enter pin"
assert msg["event"]["dsk"] == "test"
event = Event(
type="inclusion stopped",
data={
"source": "controller",
"event": "inclusion stopped",
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "inclusion stopped"
# 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
assert (
device_registry.async_get_device(
identifiers={(DOMAIN, "3245146787-67")},
)
is None
)
client.driver.receive_event(nortek_thermostat_added_event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "node added"
node_details = {
"node_id": 67,
"status": 0,
"ready": False,
}
assert msg["event"]["node"] == node_details
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "device registered"
# Check the keys of the device item
assert list(msg["event"]["device"]) == ["name", "id", "manufacturer", "model"]
# Test receiving interview events
event = Event(
type="interview started",
data={"source": "node", "event": "interview started", "nodeId": 67},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview started"
event = Event(
type="interview stage completed",
data={
"source": "node",
"event": "interview stage completed",
"stageName": "NodeInfo",
"nodeId": 67,
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview stage completed"
assert msg["event"]["stage"] == "NodeInfo"
event = Event(
type="interview completed",
data={"source": "node", "event": "interview completed", "nodeId": 67},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview completed"
event = Event(
type="interview failed",
data={
"source": "node",
"event": "interview failed",
"nodeId": 67,
"args": {
"errorMessage": "error",
"isFinal": True,
},
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview failed"
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test S2 planned provisioning entry
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/replace_failed_node",
DEVICE_ID: device.id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
PLANNED_PROVISIONING_ENTRY: {
DSK: "test",
SECURITY_CLASSES: [0],
},
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.replace_failed_node",
"nodeId": 67,
"options": {
"strategy": InclusionStrategy.SECURITY_S2,
"provisioning": ProvisioningEntry(
"test", [SecurityClass.S2_UNAUTHENTICATED]
).to_dict(),
},
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test S2 QR provisioning information
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/replace_failed_node",
DEVICE_ID: device.id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
QR_PROVISIONING_INFORMATION: {
VERSION: 0,
SECURITY_CLASSES: [0],
DSK: "test",
GENERIC_DEVICE_CLASS: 1,
SPECIFIC_DEVICE_CLASS: 1,
INSTALLER_ICON_TYPE: 1,
MANUFACTURER_ID: 1,
PRODUCT_TYPE: 1,
PRODUCT_ID: 1,
APPLICATION_VERSION: "test",
},
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.replace_failed_node",
"nodeId": 67,
"options": {
"strategy": InclusionStrategy.SECURITY_S2,
"provisioning": QRProvisioningInformation(
version=QRCodeVersion.S2,
security_classes=[SecurityClass.S2_UNAUTHENTICATED],
dsk="test",
generic_device_class=1,
specific_device_class=1,
installer_icon_type=1,
manufacturer_id=1,
product_type=1,
product_id=1,
application_version="test",
max_inclusion_request_interval=None,
uuid=None,
supported_protocols=None,
).to_dict(),
},
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test S2 QR code string
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/replace_failed_node",
DEVICE_ID: device.id,
INCLUSION_STRATEGY: InclusionStrategy.SECURITY_S2.value,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.replace_failed_node",
"nodeId": 67,
"options": {
"strategy": InclusionStrategy.SECURITY_S2,
"provisioning": "90testtesttesttesttesttesttesttesttesttesttesttesttest",
},
}
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": True}
# Test ValueError is caught as failure
await ws_client.send_json(
{
ID: 6,
TYPE: "zwave_js/replace_failed_node",
DEVICE_ID: device.id,
INCLUSION_STRATEGY: InclusionStrategy.DEFAULT.value,
QR_CODE_STRING: "90testtesttesttesttesttesttesttesttesttesttesttesttest",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert len(client.async_send_command.call_args_list) == 0
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_replace_failed_node",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 7,
TYPE: "zwave_js/replace_failed_node",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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: 8,
TYPE: "zwave_js/replace_failed_node",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_remove_failed_node(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
nortek_thermostat,
integration,
client,
hass_ws_client: WebSocketGenerator,
nortek_thermostat_removed_event,
nortek_thermostat_added_event,
) -> None:
"""Test the remove_failed_node websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, nortek_thermostat)
client.async_send_command.return_value = {"success": True}
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_remove_failed_node",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/remove_failed_node",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: Z-Wave error 1 - error message"
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/remove_failed_node",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
# Create device registry entry for mock node
device = device_registry.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
assert (
device_registry.async_get_device(
identifiers={(DOMAIN, "3245146787-67")},
)
is None
)
# Re-add node so we can test config entry not loaded
client.driver.receive_event(nortek_thermostat_added_event)
# 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/remove_failed_node",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_begin_rebuilding_routes(
hass: HomeAssistant,
integration,
client,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the begin_rebuilding_routes 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/begin_rebuilding_routes",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"]
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_begin_rebuilding_routes",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/begin_rebuilding_routes",
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"] == "zwave_error: 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/begin_rebuilding_routes",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_subscribe_rebuild_routes_progress(
hass: HomeAssistant,
integration,
client,
nortek_thermostat,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the subscribe_rebuild_routes_progress command."""
entry = integration
ws_client = await hass_ws_client(hass)
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/subscribe_rebuild_routes_progress",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] is None
# Fire rebuild routes progress
event = Event(
"rebuild routes progress",
{
"source": "controller",
"event": "rebuild routes progress",
"progress": {67: "pending"},
},
)
client.driver.controller.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "rebuild routes progress"
assert msg["event"]["rebuild_routes_status"] == {"67": "pending"}
# 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/subscribe_rebuild_routes_progress",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_subscribe_rebuild_routes_progress_initial_value(
hass: HomeAssistant,
integration,
client,
nortek_thermostat,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test subscribe_rebuild_routes_progress command when rebuild routes in progress."""
entry = integration
ws_client = await hass_ws_client(hass)
assert not client.driver.controller.rebuild_routes_progress
# Fire rebuild routes progress before sending rebuild routes progress command
event = Event(
"rebuild routes progress",
{
"source": "controller",
"event": "rebuild routes progress",
"progress": {67: "pending"},
},
)
client.driver.controller.receive_event(event)
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/subscribe_rebuild_routes_progress",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] == {"67": "pending"}
async def test_stop_rebuilding_routes(
hass: HomeAssistant,
integration,
client,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the stop_rebuilding_routes 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/stop_rebuilding_routes",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"]
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_stop_rebuilding_routes",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/stop_rebuilding_routes",
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"] == "zwave_error: 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/stop_rebuilding_routes",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_rebuild_node_routes(
hass: HomeAssistant,
multisensor_6,
integration,
client,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the rebuild_node_routes websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
client.async_send_command.return_value = {"success": True}
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/rebuild_node_routes",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"]
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_rebuild_node_routes",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/rebuild_node_routes",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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/rebuild_node_routes",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_refresh_node_info(
hass: HomeAssistant,
client,
multisensor_6,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that the refresh_node_info WS API call works."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
client.async_send_command_no_wait.return_value = None
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/refresh_node_info",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command_no_wait.call_args_list) == 1
args = client.async_send_command_no_wait.call_args[0][0]
assert args["command"] == "node.refresh_info"
assert args["nodeId"] == 52
event = Event(
type="interview started",
data={"source": "node", "event": "interview started", "nodeId": 52},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview started"
event = Event(
type="interview stage completed",
data={
"source": "node",
"event": "interview stage completed",
"stageName": "NodeInfo",
"nodeId": 52,
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview stage completed"
assert msg["event"]["stage"] == "NodeInfo"
event = Event(
type="interview completed",
data={"source": "node", "event": "interview completed", "nodeId": 52},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview completed"
event = Event(
type="interview failed",
data={
"source": "node",
"event": "interview failed",
"nodeId": 52,
"args": {
"errorMessage": "error",
"isFinal": True,
},
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"]["event"] == "interview failed"
client.async_send_command_no_wait.reset_mock()
# Test getting non-existent node fails
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/refresh_node_info",
DEVICE_ID: "fake_device",
}
)
msg = await ws_client.receive_json()
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",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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/refresh_node_info",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_refresh_node_values(
hass: HomeAssistant,
client,
multisensor_6,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that the refresh_node_values WS API call works."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
client.async_send_command_no_wait.return_value = None
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/refresh_node_values",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command_no_wait.call_args_list) == 1
args = client.async_send_command_no_wait.call_args[0][0]
assert args["command"] == "node.refresh_values"
assert args["nodeId"] == 52
client.async_send_command_no_wait.reset_mock()
# Test getting non-existent device fails
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/refresh_node_values",
DEVICE_ID: "fake_device",
}
)
msg = await ws_client.receive_json()
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",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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",
DEVICE_ID: device.id,
}
)
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: HomeAssistant,
multisensor_6,
client,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that the refresh_node_cc_values WS API call works."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
client.async_send_command_no_wait.return_value = None
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/refresh_node_cc_values",
DEVICE_ID: device.id,
COMMAND_CLASS_ID: 112,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command_no_wait.call_args_list) == 1
args = client.async_send_command_no_wait.call_args[0][0]
assert args["command"] == "node.refresh_cc_values"
assert args["nodeId"] == 52
assert args["commandClass"] == 112
client.async_send_command_no_wait.reset_mock()
# Test using invalid CC ID fails
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/refresh_node_cc_values",
DEVICE_ID: device.id,
COMMAND_CLASS_ID: 9999,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test getting non-existent device fails
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/refresh_node_cc_values",
DEVICE_ID: "fake_device",
COMMAND_CLASS_ID: 112,
}
)
msg = await ws_client.receive_json()
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",
DEVICE_ID: device.id,
COMMAND_CLASS_ID: 112,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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_cc_values",
DEVICE_ID: device.id,
COMMAND_CLASS_ID: 112,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_set_config_parameter(
hass: HomeAssistant,
multisensor_6,
client,
hass_ws_client: WebSocketGenerator,
integration,
) -> None:
"""Test the set_config_parameter service."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
new_value_data = multisensor_6.values[
get_value_id_str(multisensor_6, 112, 102, 0, 1)
].data.copy()
new_value_data["endpoint"] = 1
new_value = ConfigurationValue(multisensor_6, new_value_data)
multisensor_6.values[get_value_id_str(multisensor_6, 112, 102, 1, 1)] = new_value
client.async_send_command_no_wait.return_value = None
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
PROPERTY_KEY: 1,
VALUE: 1,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"]["status"] == "queued"
assert len(client.async_send_command_no_wait.call_args_list) == 1
args = client.async_send_command_no_wait.call_args[0][0]
assert args["command"] == "node.set_value"
assert args["nodeId"] == 52
assert args["valueId"] == {
"commandClass": 112,
"endpoint": 0,
"property": 102,
"propertyKey": 1,
}
assert args["value"] == 1
client.async_send_command_no_wait.reset_mock()
client.async_send_command_no_wait.return_value = None
# Test using a different endpoint
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
ENDPOINT: 1,
PROPERTY: 102,
PROPERTY_KEY: 1,
VALUE: 1,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"]["status"] == "queued"
assert len(client.async_send_command_no_wait.call_args_list) == 1
args = client.async_send_command_no_wait.call_args[0][0]
assert args["command"] == "node.set_value"
assert args["nodeId"] == 52
assert args["valueId"] == {
"commandClass": 112,
"endpoint": 1,
"property": 102,
"propertyKey": 1,
}
assert args["value"] == 1
client.async_send_command_no_wait.reset_mock()
# Test that hex strings are accepted and converted as expected
client.async_send_command_no_wait.return_value = None
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
PROPERTY_KEY: 1,
VALUE: "0x1",
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"]["status"] == "queued"
assert len(client.async_send_command_no_wait.call_args_list) == 1
args = client.async_send_command_no_wait.call_args[0][0]
assert args["command"] == "node.set_value"
assert args["nodeId"] == 52
assert args["valueId"] == {
"commandClass": 112,
"endpoint": 0,
"property": 102,
"propertyKey": 1,
}
assert args["value"] == 1
client.async_send_command_no_wait.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: 4,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
PROPERTY_KEY: 1,
VALUE: 1,
}
)
msg = await ws_client.receive_json()
assert len(client.async_send_command_no_wait.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: 5,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
PROPERTY_KEY: 1,
VALUE: 1,
}
)
msg = await ws_client.receive_json()
assert len(client.async_send_command_no_wait.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: 6,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
PROPERTY_KEY: 1,
VALUE: 1,
}
)
msg = await ws_client.receive_json()
assert len(client.async_send_command_no_wait.call_args_list) == 0
assert not msg["success"]
assert msg["error"]["code"] == "unknown_error"
assert msg["error"]["message"] == "test"
# Test getting non-existent node fails
await ws_client.send_json(
{
ID: 7,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: "fake_device",
PROPERTY: 102,
PROPERTY_KEY: 1,
VALUE: 1,
}
)
msg = await ws_client.receive_json()
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: 8,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
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"] == "zwave_error: 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: 9,
TYPE: "zwave_js/set_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
PROPERTY_KEY: 1,
VALUE: 1,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_get_config_parameters(
hass: HomeAssistant, multisensor_6, integration, hass_ws_client: WebSocketGenerator
) -> None:
"""Test the get config parameters websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
node = multisensor_6
device = get_device(hass, node)
# Test getting configuration parameter values
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/get_config_parameters",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
result = msg["result"]
assert len(result) == 61
key = "52-112-0-2"
assert result[key]["property"] == 2
assert result[key]["property_key"] is None
assert result[key]["endpoint"] == 0
assert result[key]["configuration_value_type"] == "enumerated"
assert result[key]["metadata"]["states"]
assert (
result[key]["metadata"]["description"]
== "Stay awake for 10 minutes at power on"
)
assert result[key]["metadata"]["label"] == "Stay Awake in Battery Mode"
assert result[key]["metadata"]["type"] == "number"
assert result[key]["metadata"]["min"] == 0
assert result[key]["metadata"]["max"] == 1
assert result[key]["metadata"]["unit"] is None
assert result[key]["metadata"]["writeable"] is True
assert result[key]["metadata"]["readable"] is True
assert result[key]["metadata"]["default"] == 0
assert result[key]["value"] == 0
key = "52-112-0-201-255"
assert result[key]["property_key"] == 255
# Test getting non-existent node config params fails
await ws_client.send_json(
{
ID: 5,
TYPE: "zwave_js/get_config_parameters",
DEVICE_ID: "fake_device",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# 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/get_config_parameters",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_set_raw_config_parameter(
hass: HomeAssistant,
client,
multisensor_6,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that the set_raw_config_parameter WS API call works."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
# Change from async_send_command to async_send_command_no_wait
client.async_send_command_no_wait.return_value = None
# Test setting a raw config parameter value
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/set_raw_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
VALUE: 1,
VALUE_SIZE: 2,
VALUE_FORMAT: 1,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"]["status"] == "queued"
assert len(client.async_send_command_no_wait.call_args_list) == 1
args = client.async_send_command_no_wait.call_args[0][0]
assert args["command"] == "endpoint.set_raw_config_parameter_value"
assert args["nodeId"] == multisensor_6.node_id
assert args["options"]["parameter"] == 102
assert args["options"]["value"] == 1
assert args["options"]["valueSize"] == 2
assert args["options"]["valueFormat"] == 1
# Reset the mock for async_send_command_no_wait instead
client.async_send_command_no_wait.reset_mock()
# Test getting non-existent node fails
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/set_raw_config_parameter",
DEVICE_ID: "fake_device",
PROPERTY: 102,
VALUE: 1,
VALUE_SIZE: 2,
VALUE_FORMAT: 1,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# 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_auto_id(
{
TYPE: "zwave_js/set_raw_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
VALUE: 1,
VALUE_SIZE: 2,
VALUE_FORMAT: 1,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_get_raw_config_parameter(
hass: HomeAssistant,
multisensor_6,
integration,
client,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the get_raw_config_parameter websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
client.async_send_command.return_value = {"value": 1}
# Test getting a raw config parameter value
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/get_raw_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"]["value"] == 1
assert len(client.async_send_command.call_args_list) == 1
args = client.async_send_command.call_args[0][0]
assert args["command"] == "endpoint.get_raw_config_parameter_value"
assert args["nodeId"] == multisensor_6.node_id
assert args["options"]["parameter"] == 102
client.async_send_command.reset_mock()
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.node.Node.async_get_raw_config_parameter_value",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/get_raw_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: Z-Wave error 1 - error message"
# Test getting non-existent node fails
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/get_raw_config_parameter",
DEVICE_ID: "fake_device",
PROPERTY: 102,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# Test FailedCommand exception
client.async_send_command.side_effect = FailedCommand("test", "test")
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/get_raw_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "test"
assert msg["error"]["message"] == "Command failed: test"
# 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_auto_id(
{
TYPE: "zwave_js/get_raw_config_parameter",
DEVICE_ID: device.id,
PROPERTY: 102,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
@pytest.mark.parametrize(
("firmware_data", "expected_data"),
[({"target": "1"}, {"firmware_target": 1}), ({}, {})],
)
async def test_firmware_upload_view(
hass: HomeAssistant,
multisensor_6,
integration,
hass_client: ClientSessionGenerator,
firmware_file,
firmware_data: dict[str, Any],
expected_data: dict[str, Any],
) -> None:
"""Test the HTTP firmware upload view."""
client = await hass_client()
device = get_device(hass, multisensor_6)
with (
patch(
"homeassistant.components.zwave_js.api.update_firmware",
) as mock_node_cmd,
patch(
"homeassistant.components.zwave_js.api.controller_firmware_update_otw",
) as mock_controller_cmd,
patch.dict(
"homeassistant.components.zwave_js.api.USER_AGENT",
{"HomeAssistant": "0.0.0"},
),
):
data = {"file": firmware_file}
data.update(firmware_data)
resp = await client.post(
f"/api/zwave_js/firmware/upload/{device.id}", data=data
)
update_data = NodeFirmwareUpdateData(
"file", b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
)
for attr, value in expected_data.items():
setattr(update_data, attr, value)
mock_controller_cmd.assert_not_called()
assert mock_node_cmd.call_args[0][1:3] == (multisensor_6, [update_data])
assert mock_node_cmd.call_args[1] == {
"additional_user_agent_components": {"HomeAssistant": "0.0.0"},
}
assert json.loads(await resp.text()) is None
async def test_firmware_upload_view_controller(
hass: HomeAssistant,
client,
integration,
hass_client: ClientSessionGenerator,
firmware_file,
) -> None:
"""Test the HTTP firmware upload view for a controller."""
hass_client = await hass_client()
device = get_device(hass, client.driver.controller.nodes[1])
with (
patch(
"homeassistant.components.zwave_js.api.update_firmware",
) as mock_node_cmd,
patch(
"homeassistant.components.zwave_js.api.controller_firmware_update_otw",
) as mock_controller_cmd,
patch.dict(
"homeassistant.components.zwave_js.api.USER_AGENT",
{"HomeAssistant": "0.0.0"},
),
):
resp = await hass_client.post(
f"/api/zwave_js/firmware/upload/{device.id}",
data={"file": firmware_file},
)
mock_node_cmd.assert_not_called()
assert mock_controller_cmd.call_args[0][1:2] == (
ControllerFirmwareUpdateData(
"file", b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
),
)
assert mock_controller_cmd.call_args[1] == {
"additional_user_agent_components": {"HomeAssistant": "0.0.0"},
}
assert json.loads(await resp.text()) is None
async def test_firmware_upload_view_failed_command(
hass: HomeAssistant,
multisensor_6,
integration,
hass_client: ClientSessionGenerator,
firmware_file,
) -> None:
"""Test failed command for the HTTP firmware upload view."""
client = await hass_client()
device = get_device(hass, multisensor_6)
with patch(
"homeassistant.components.zwave_js.api.update_firmware",
side_effect=FailedCommand("test", "test"),
):
resp = await client.post(
f"/api/zwave_js/firmware/upload/{device.id}",
data={"file": firmware_file},
)
assert resp.status == HTTPStatus.BAD_REQUEST
async def test_firmware_upload_view_invalid_payload(
hass: HomeAssistant, multisensor_6, integration, hass_client: ClientSessionGenerator
) -> None:
"""Test an invalid payload for the HTTP firmware upload view."""
device = get_device(hass, multisensor_6)
client = await hass_client()
resp = await client.post(
f"/api/zwave_js/firmware/upload/{device.id}",
data={"wrong_key": BytesIO(bytes(10))},
)
assert resp.status == HTTPStatus.BAD_REQUEST
async def test_firmware_upload_view_no_driver(
hass: HomeAssistant,
client,
multisensor_6,
integration,
hass_client: ClientSessionGenerator,
) -> None:
"""Test the HTTP firmware upload view when the driver doesn't exist."""
device = get_device(hass, multisensor_6)
client.driver = None
aiohttp_client = await hass_client()
resp = await aiohttp_client.post(
f"/api/zwave_js/firmware/upload/{device.id}",
data={"wrong_key": BytesIO(bytes(10))},
)
assert resp.status == HTTPStatus.NOT_FOUND
@pytest.mark.parametrize(
("method", "url"),
[("post", "/api/zwave_js/firmware/upload/{}")],
)
async def test_node_view_non_admin_user(
hass: HomeAssistant,
multisensor_6,
integration,
hass_client: ClientSessionGenerator,
hass_admin_user: MockUser,
method,
url,
) -> None:
"""Test node level views for non-admin users."""
client = await hass_client()
device = get_device(hass, multisensor_6)
# Verify we require admin user
hass_admin_user.groups = []
resp = await client.request(method, url.format(device.id))
assert resp.status == HTTPStatus.UNAUTHORIZED
@pytest.mark.parametrize(
("method", "url"),
[
("post", "/api/zwave_js/firmware/upload/{}"),
],
)
async def test_view_unloaded_config_entry(
hass: HomeAssistant,
multisensor_6,
integration,
hass_client: ClientSessionGenerator,
method,
url,
) -> None:
"""Test an unloaded config entry raises Bad Request."""
client = await hass_client()
device = get_device(hass, multisensor_6)
await hass.config_entries.async_unload(integration.entry_id)
resp = await client.request(method, url.format(device.id))
assert resp.status == HTTPStatus.BAD_REQUEST
@pytest.mark.parametrize(
("method", "url"),
[("post", "/api/zwave_js/firmware/upload/INVALID")],
)
async def test_view_invalid_device_id(
integration, hass_client: ClientSessionGenerator, method, url
) -> None:
"""Test an invalid device id parameter."""
client = await hass_client()
resp = await client.request(method, url.format(integration.entry_id))
assert resp.status == HTTPStatus.NOT_FOUND
async def test_subscribe_log_updates(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test the subscribe_log_updates websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {}
await ws_client.send_json(
{ID: 1, TYPE: "zwave_js/subscribe_log_updates", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
assert msg["success"]
event = Event(
type="logging",
data={
"source": "driver",
"event": "logging",
"message": "test",
"formattedMessage": "test",
"direction": ">",
"level": "debug",
"primaryTags": "tag",
"secondaryTags": "tag2",
"secondaryTagPadding": 0,
"multiline": False,
"timestamp": "time",
"label": "label",
"context": {"source": "config"},
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"] == {
"type": "log_message",
"log_message": {
"message": ["test"],
"level": "debug",
"primary_tags": "tag",
"timestamp": "time",
},
}
event = Event(
type="log config updated",
data={
"source": "driver",
"event": "log config updated",
"config": {
"enabled": False,
"level": "error",
"logToFile": True,
"filename": "test",
"forceConsole": True,
},
},
)
client.driver.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"] == {
"type": "log_config",
"log_config": {
"enabled": False,
"level": "error",
"log_to_file": True,
"filename": "test",
"force_console": True,
},
}
# Test FailedZWaveCommand is caught
client.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"] == "zwave_error: 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/subscribe_log_updates", ENTRY_ID: entry.entry_id}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_update_log_config(
hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator
) -> None:
"""Test that update_log_config WS API call and 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"] == "driver.update_log_config"
assert args["config"] == {"level": "error"}
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"] == "driver.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"] == "driver.update_log_config"
assert args["config"] == {
"level": "error",
"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 msg["error"]["code"] == "invalid_format"
# 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"]
)
# 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"] == "zwave_error: 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: 8,
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"] == ERR_NOT_LOADED
async def test_get_log_config(
hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator
) -> None:
"""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
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.INFO
assert log_config["log_to_file"] is False
assert log_config["filename"] == ""
assert log_config["force_console"] is False
# 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/get_log_config",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_data_collection(
hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator
) -> None:
"""Test that the data collection WS API commands work."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {"statisticsEnabled": False}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/data_collection_status",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
result = msg["result"]
assert result == {"opted_in": None, "enabled": False}
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "driver.is_statistics_enabled"
}
assert CONF_DATA_COLLECTION_OPTED_IN not in entry.data
client.async_send_command.reset_mock()
client.async_send_command.return_value = {}
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/update_data_collection_preference",
ENTRY_ID: entry.entry_id,
OPTED_IN: True,
}
)
msg = await ws_client.receive_json()
result = msg["result"]
assert result is None
assert len(client.async_send_command.call_args_list) == 1
args = client.async_send_command.call_args_list[0][0][0]
assert args["command"] == "driver.enable_statistics"
assert args["applicationName"] == "Home Assistant"
client.async_send_command.reset_mock()
client.async_send_command.return_value = {}
await ws_client.send_json(
{
ID: 3,
TYPE: "zwave_js/update_data_collection_preference",
ENTRY_ID: entry.entry_id,
OPTED_IN: False,
}
)
msg = await ws_client.receive_json()
result = msg["result"]
assert result is None
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "driver.disable_statistics"
}
assert not entry.data[CONF_DATA_COLLECTION_OPTED_IN]
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"] == "zwave_error: 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"] == "zwave_error: 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/data_collection_status",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
await ws_client.send_json(
{
ID: 7,
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"] == ERR_NOT_LOADED
async def test_abort_firmware_update(
hass: HomeAssistant,
client,
multisensor_6,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that the abort_firmware_update WS API call works."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/abort_firmware_update",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert len(client.async_send_command_no_wait.call_args_list) == 1
args = client.async_send_command_no_wait.call_args[0][0]
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",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
# Test sending command with improper device ID fails
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/abort_firmware_update",
DEVICE_ID: "fake_device",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
async def test_is_node_firmware_update_in_progress(
hass: HomeAssistant,
client,
multisensor_6,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that the is_firmware_update_in_progress WS API call works."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
client.async_send_command.return_value = {"progress": True}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/is_node_firmware_update_in_progress",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
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.is_firmware_update_in_progress"
assert args["nodeId"] == multisensor_6.node_id
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.node.Node.async_is_firmware_update_in_progress",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/is_node_firmware_update_in_progress",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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/is_node_firmware_update_in_progress",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_subscribe_firmware_update_status(
hass: HomeAssistant,
multisensor_6,
integration,
client,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the subscribe_firmware_update_status websocket command."""
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
client.async_send_command_no_wait.return_value = {}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/subscribe_firmware_update_status",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] is None
event = Event(
type="firmware update progress",
data={
"source": "node",
"event": "firmware update progress",
"nodeId": multisensor_6.node_id,
"progress": {
"currentFile": 1,
"totalFiles": 1,
"sentFragments": 1,
"totalFragments": 10,
"progress": 10.0,
},
},
)
multisensor_6.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "firmware update progress",
"current_file": 1,
"total_files": 1,
"sent_fragments": 1,
"total_fragments": 10,
"progress": 10.0,
}
event = Event(
type="firmware update finished",
data={
"source": "node",
"event": "firmware update finished",
"nodeId": multisensor_6.node_id,
"result": {
"status": 255,
"success": True,
"waitTime": 10,
"reInterview": False,
},
},
)
multisensor_6.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "firmware update finished",
"status": 255,
"success": True,
"wait_time": 10,
"reinterview": False,
}
async def test_subscribe_firmware_update_status_initial_value(
hass: HomeAssistant,
multisensor_6,
client,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test subscribe_firmware_update_status WS command with in progress update."""
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
assert multisensor_6.firmware_update_progress is None
# Send a firmware update progress event before the WS command
event = Event(
type="firmware update progress",
data={
"source": "node",
"event": "firmware update progress",
"nodeId": multisensor_6.node_id,
"progress": {
"currentFile": 1,
"totalFiles": 1,
"sentFragments": 1,
"totalFragments": 10,
"progress": 10.0,
},
},
)
multisensor_6.receive_event(event)
client.async_send_command_no_wait.return_value = {}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/subscribe_firmware_update_status",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] is None
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "firmware update progress",
"current_file": 1,
"total_files": 1,
"sent_fragments": 1,
"total_fragments": 10,
"progress": 10.0,
}
async def test_subscribe_controller_firmware_update_status(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test the subscribe_firmware_update_status websocket command for a node."""
ws_client = await hass_ws_client(hass)
device = get_device(hass, client.driver.controller.nodes[1])
client.async_send_command_no_wait.return_value = {}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/subscribe_firmware_update_status",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] is None
event = Event(
type="firmware update progress",
data={
"source": "controller",
"event": "firmware update progress",
"progress": {
"sentFragments": 1,
"totalFragments": 10,
"progress": 10.0,
},
},
)
client.driver.controller.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "firmware update progress",
"current_file": 1,
"total_files": 1,
"sent_fragments": 1,
"total_fragments": 10,
"progress": 10.0,
}
event = Event(
type="firmware update finished",
data={
"source": "controller",
"event": "firmware update finished",
"result": {
"status": 255,
"success": True,
},
},
)
client.driver.controller.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "firmware update finished",
"status": 255,
"success": True,
}
async def test_subscribe_controller_firmware_update_status_initial_value(
hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator
) -> None:
"""Test subscribe_firmware_update_status cmd with in progress update for node."""
ws_client = await hass_ws_client(hass)
device = get_device(hass, client.driver.controller.nodes[1])
assert client.driver.controller.firmware_update_progress is None
# Send a firmware update progress event before the WS command
event = Event(
type="firmware update progress",
data={
"source": "controller",
"event": "firmware update progress",
"progress": {
"sentFragments": 1,
"totalFragments": 10,
"progress": 10.0,
},
},
)
client.driver.controller.receive_event(event)
client.async_send_command_no_wait.return_value = {}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/subscribe_firmware_update_status",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] is None
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "firmware update progress",
"current_file": 1,
"total_files": 1,
"sent_fragments": 1,
"total_fragments": 10,
"progress": 10.0,
}
async def test_subscribe_firmware_update_status_failures(
hass: HomeAssistant,
multisensor_6,
client,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test failures for the subscribe_firmware_update_status websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
# Test sending command with improper entry ID fails
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/subscribe_firmware_update_status",
DEVICE_ID: "fake_device",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# 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/subscribe_firmware_update_status",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_get_node_firmware_update_capabilities(
hass: HomeAssistant,
client,
multisensor_6,
integration,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that the get_node_firmware_update_capabilities WS API call works."""
entry = integration
ws_client = await hass_ws_client(hass)
device = get_device(hass, multisensor_6)
client.async_send_command.return_value = {
"capabilities": {
"firmwareUpgradable": True,
"firmwareTargets": [0],
"continuesToFunction": True,
"supportsActivation": True,
}
}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/get_node_firmware_update_capabilities",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] == {
"firmware_upgradable": True,
"firmware_targets": [0],
"continues_to_function": True,
"supports_activation": True,
}
assert len(client.async_send_command.call_args_list) == 1
args = client.async_send_command.call_args[0][0]
assert args["command"] == "node.get_firmware_update_capabilities"
assert args["nodeId"] == multisensor_6.node_id
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.node.Node.async_get_firmware_update_capabilities",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/get_node_firmware_update_capabilities",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == "zwave_error"
assert msg["error"]["message"] == "zwave_error: 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/get_node_firmware_update_capabilities",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
# Test sending command with improper device ID fails
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/get_node_firmware_update_capabilities",
DEVICE_ID: "fake_device",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
async def test_is_any_ota_firmware_update_in_progress(
hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator
) -> None:
"""Test that the is_any_ota_firmware_update_in_progress WS API call works."""
entry = integration
ws_client = await hass_ws_client(hass)
client.async_send_command.return_value = {"progress": True}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/is_any_ota_firmware_update_in_progress",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
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"] == "controller.is_any_ota_firmware_update_in_progress"
# Test FailedZWaveCommand is caught
with patch(
f"{CONTROLLER_PATCH_PREFIX}.async_is_any_ota_firmware_update_in_progress",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/is_any_ota_firmware_update_in_progress",
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"] == "zwave_error: 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/is_any_ota_firmware_update_in_progress",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
# Test sending command with improper device ID fails
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/is_any_ota_firmware_update_in_progress",
ENTRY_ID: "invalid_entry",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
async def test_check_for_config_updates(
hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator
) -> None:
"""Test that the check_for_config_updates 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 = {
"updateAvailable": True,
"newVersion": "test",
"installedVersion": "test",
}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/check_for_config_updates",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert msg["result"]
assert msg["success"]
config_update = msg["result"]
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"] == "zwave_error: 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/check_for_config_updates",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/check_for_config_updates",
ENTRY_ID: "INVALID",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
async def test_install_config_update(
hass: HomeAssistant, client, integration, hass_ws_client: WebSocketGenerator
) -> None:
"""Test that the install_config_update 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}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/install_config_update",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
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"] == "zwave_error: 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/install_config_update",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/install_config_update",
ENTRY_ID: "INVALID",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
async def test_subscribe_controller_statistics(
hass: HomeAssistant, integration, client, hass_ws_client: WebSocketGenerator
) -> None:
"""Test the subscribe_controller_statistics command."""
entry = integration
ws_client = await hass_ws_client(hass)
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/subscribe_controller_statistics",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] is None
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "statistics updated",
"source": "controller",
"messages_tx": 0,
"messages_rx": 0,
"messages_dropped_tx": 0,
"messages_dropped_rx": 0,
"nak": 0,
"can": 0,
"timeout_ack": 0,
"timout_response": 0,
"timeout_callback": 0,
}
# Fire statistics updated
event = Event(
"statistics updated",
{
"source": "controller",
"event": "statistics updated",
"statistics": {
"messagesTX": 1,
"messagesRX": 1,
"messagesDroppedTX": 1,
"messagesDroppedRX": 1,
"NAK": 1,
"CAN": 1,
"timeoutACK": 1,
"timeoutResponse": 1,
"timeoutCallback": 1,
},
},
)
client.driver.controller.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "statistics updated",
"source": "controller",
"messages_tx": 1,
"messages_rx": 1,
"messages_dropped_tx": 1,
"messages_dropped_rx": 1,
"nak": 1,
"can": 1,
"timeout_ack": 1,
"timout_response": 1,
"timeout_callback": 1,
}
# Test sending command with improper entry ID fails
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/subscribe_controller_statistics",
ENTRY_ID: "fake_entry_id",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# 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/subscribe_controller_statistics",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_subscribe_node_statistics(
hass: HomeAssistant,
multisensor_6,
wallmote_central_scene,
zen_31,
integration,
client,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the subscribe_node_statistics command."""
entry = integration
ws_client = await hass_ws_client(hass)
multisensor_6_device = get_device(hass, multisensor_6)
zen_31_device = get_device(hass, zen_31)
wallmote_central_scene_device = get_device(hass, wallmote_central_scene)
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/subscribe_node_statistics",
DEVICE_ID: multisensor_6_device.id,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] is None
msg = await ws_client.receive_json()
assert msg["event"] == {
"source": "node",
"event": "statistics updated",
"nodeId": multisensor_6.node_id,
"commands_tx": 0,
"commands_rx": 0,
"commands_dropped_tx": 0,
"commands_dropped_rx": 0,
"timeout_response": 0,
"rtt": None,
"rssi": None,
"lwr": None,
"nlwr": None,
}
# Fire statistics updated
event = Event(
"statistics updated",
{
"source": "node",
"event": "statistics updated",
"nodeId": multisensor_6.node_id,
"statistics": {
"commandsTX": 1,
"commandsRX": 2,
"commandsDroppedTX": 3,
"commandsDroppedRX": 4,
"timeoutResponse": 5,
"rtt": 6,
"rssi": 7,
"lwr": {
"protocolDataRate": 1,
"rssi": 1,
"repeaters": [wallmote_central_scene.node_id],
"repeaterRSSI": [1],
"routeFailedBetween": [
zen_31.node_id,
multisensor_6.node_id,
],
},
"nlwr": {
"protocolDataRate": 2,
"rssi": 2,
"repeaters": [],
"repeaterRSSI": [127],
"routeFailedBetween": [
multisensor_6.node_id,
zen_31.node_id,
],
},
},
},
)
client.driver.controller.receive_event(event)
msg = await ws_client.receive_json()
assert msg["event"] == {
"event": "statistics updated",
"source": "node",
"node_id": multisensor_6.node_id,
"commands_tx": 1,
"commands_rx": 2,
"commands_dropped_tx": 3,
"commands_dropped_rx": 4,
"timeout_response": 5,
"rtt": 6,
"rssi": 7,
"lwr": {
"protocol_data_rate": 1,
"rssi": 1,
"repeaters": [wallmote_central_scene_device.id],
"repeater_rssi": [1],
"route_failed_between": [
zen_31_device.id,
multisensor_6_device.id,
],
},
"nlwr": {
"protocol_data_rate": 2,
"rssi": 2,
"repeaters": [],
"repeater_rssi": [127],
"route_failed_between": [
multisensor_6_device.id,
zen_31_device.id,
],
},
}
# Test sending command with improper entry ID fails
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/subscribe_node_statistics",
DEVICE_ID: "fake_device",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# 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/subscribe_node_statistics",
DEVICE_ID: multisensor_6_device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_hard_reset_controller(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
client,
integration,
listen_block,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that the hard_reset_controller WS API call works."""
entry = integration
ws_client = await hass_ws_client(hass)
device = device_registry.async_get_device(
identifiers={get_device_id(client.driver, client.driver.controller.nodes[1])}
)
client.async_send_command.return_value = {}
await ws_client.send_json(
{
ID: 1,
TYPE: "zwave_js/hard_reset_controller",
ENTRY_ID: entry.entry_id,
}
)
listen_block.set()
listen_block.clear()
await hass.async_block_till_done()
msg = await ws_client.receive_json()
assert msg["result"] == device.id
assert msg["success"]
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {"command": "driver.hard_reset"}
# Test FailedZWaveCommand is caught
with patch(
"zwave_js_server.model.driver.Driver.async_hard_reset",
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
):
await ws_client.send_json(
{
ID: 2,
TYPE: "zwave_js/hard_reset_controller",
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"] == "zwave_error: 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/hard_reset_controller",
ENTRY_ID: entry.entry_id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
await ws_client.send_json(
{
ID: 4,
TYPE: "zwave_js/hard_reset_controller",
ENTRY_ID: "INVALID",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
async def test_node_capabilities(
hass: HomeAssistant,
multisensor_6: Node,
integration: MockConfigEntry,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the node_capabilities websocket command."""
entry = integration
ws_client = await hass_ws_client(hass)
node = multisensor_6
device = get_device(hass, node)
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/node_capabilities",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert msg["result"] == {
"0": [
{
"id": 113,
"name": "Notification",
"version": 8,
"isSecure": False,
"is_secure": False,
}
]
}
# Test getting non-existent node fails
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/node_status",
DEVICE_ID: "fake_device",
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_FOUND
# 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_auto_id(
{
TYPE: "zwave_js/node_status",
DEVICE_ID: device.id,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"]["code"] == ERR_NOT_LOADED
async def test_invoke_cc_api(
hass: HomeAssistant,
client,
climate_radio_thermostat_ct100_plus_different_endpoints: Node,
integration: MockConfigEntry,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test the invoke_cc_api websocket command."""
ws_client = await hass_ws_client(hass)
device_radio_thermostat = get_device(
hass, climate_radio_thermostat_ct100_plus_different_endpoints
)
assert device_radio_thermostat
# Test successful invoke_cc_api call with a static endpoint
client.async_send_command.return_value = {"response": True}
client.async_send_command_no_wait.return_value = {"response": True}
# Test with wait_for_result=False (default)
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/invoke_cc_api",
DEVICE_ID: device_radio_thermostat.id,
ATTR_COMMAND_CLASS: 67,
ATTR_METHOD_NAME: "someMethod",
ATTR_PARAMETERS: [1, 2],
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] is None # We did not specify wait_for_result=True
await hass.async_block_till_done()
assert len(client.async_send_command_no_wait.call_args_list) == 1
args = client.async_send_command_no_wait.call_args[0][0]
assert args == {
"command": "endpoint.invoke_cc_api",
"nodeId": 26,
"endpoint": 0,
"commandClass": 67,
"methodName": "someMethod",
"args": [1, 2],
}
client.async_send_command_no_wait.reset_mock()
# Test with wait_for_result=True
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/invoke_cc_api",
DEVICE_ID: device_radio_thermostat.id,
ATTR_COMMAND_CLASS: 67,
ATTR_ENDPOINT: 0,
ATTR_METHOD_NAME: "someMethod",
ATTR_PARAMETERS: [1, 2],
ATTR_WAIT_FOR_RESULT: True,
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] is True
await hass.async_block_till_done()
assert len(client.async_send_command.call_args_list) == 1
args = client.async_send_command.call_args[0][0]
assert args == {
"command": "endpoint.invoke_cc_api",
"nodeId": 26,
"endpoint": 0,
"commandClass": 67,
"methodName": "someMethod",
"args": [1, 2],
}
client.async_send_command.side_effect = NotFoundError
# Ensure an error is returned
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/invoke_cc_api",
DEVICE_ID: device_radio_thermostat.id,
ATTR_COMMAND_CLASS: 67,
ATTR_ENDPOINT: 0,
ATTR_METHOD_NAME: "someMethod",
ATTR_PARAMETERS: [1, 2],
ATTR_WAIT_FOR_RESULT: True,
}
)
msg = await ws_client.receive_json()
assert not msg["success"]
assert msg["error"] == {"code": "NotFoundError", "message": ""}
@pytest.mark.parametrize(
("config", "installer_mode"), [({}, False), ({CONF_INSTALLER_MODE: True}, True)]
)
async def test_get_integration_settings(
config: dict[str, Any],
installer_mode: bool,
hass: HomeAssistant,
client: MagicMock,
hass_ws_client: WebSocketGenerator,
) -> None:
"""Test that the get_integration_settings WS API call works."""
ws_client = await hass_ws_client(hass)
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
entry.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {DOMAIN: config})
await hass.async_block_till_done()
await ws_client.send_json_auto_id(
{
TYPE: "zwave_js/get_integration_settings",
}
)
msg = await ws_client.receive_json()
assert msg["success"]
assert msg["result"] == {
CONF_INSTALLER_MODE: installer_mode,
}