Bump zwave-js-server-python to 0.50.1 (#94760)

* Bump zwave-js-server-python to 0.50.0

* handle additional upstream changes

* Additional changes

* fix tests

* Convert two similar functions to be one function

* Fix docstring

* Remove conditional pydantic import

* Revert scope change

* Bump zwave-js-server-python

* Set default return value for command

* Remove line breaks

* Add coverage
This commit is contained in:
Raman Gupta 2023-08-10 01:28:08 -04:00 committed by GitHub
parent 2841cbbed2
commit 5d3d66e47d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 171 additions and 128 deletions

View file

@ -9,7 +9,7 @@ from typing import Any
from async_timeout import timeout
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import CommandClass
from zwave_js_server.const import CommandClass, RemoveNodeReason
from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.node import Node as ZwaveNode
@ -398,13 +398,13 @@ class ControllerEvents:
def async_on_node_removed(self, event: dict) -> None:
"""Handle node removed event."""
node: ZwaveNode = event["node"]
replaced: bool = event.get("replaced", False)
reason: RemoveNodeReason = event["reason"]
# grab device in device registry attached to this node
dev_id = get_device_id(self.driver_events.driver, node)
device = self.dev_reg.async_get_device(identifiers={dev_id})
# We assert because we know the device exists
assert device
if replaced:
if reason in (RemoveNodeReason.REPLACED, RemoveNodeReason.PROXY_REPLACED):
self.discovered_value_ids.pop(device.id, None)
async_dispatcher_send(

View file

@ -1138,6 +1138,7 @@ async def websocket_remove_node(
node = event["node"]
node_details = {
"node_id": node.node_id,
"reason": event["reason"],
}
connection.send_message(

View file

@ -507,8 +507,9 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
# Please use Dry and Fan HVAC modes instead.
if preset_mode_value in (ThermostatMode.DRY, ThermostatMode.FAN):
LOGGER.warning(
"Dry and Fan preset modes are deprecated and will be removed in Home Assistant 2024.2. "
"Please use the corresponding Dry and Fan HVAC modes instead"
"Dry and Fan preset modes are deprecated and will be removed in Home "
"Assistant 2024.2. Please use the corresponding Dry and Fan HVAC "
"modes instead"
)
async_create_issue(
self.hass,

View file

@ -54,9 +54,8 @@ from .device_automation_helpers import (
CONF_SUBTYPE,
VALUE_ID_REGEX,
generate_config_parameter_subtype,
get_config_parameter_value_schema,
)
from .helpers import async_get_node_from_device_id
from .helpers import async_get_node_from_device_id, get_value_state_schema
ACTION_TYPES = {
SERVICE_CLEAR_LOCK_USERCODE,
@ -357,7 +356,7 @@ async def async_get_action_capabilities(
property_key=config[ATTR_CONFIG_PARAMETER_BITMASK],
endpoint=config[ATTR_ENDPOINT],
)
value_schema = get_config_parameter_value_schema(node, value_id)
value_schema = get_value_state_schema(node.values[value_id])
if value_schema is None:
return {}
return {"extra_fields": vol.Schema({vol.Required(ATTR_VALUE): value_schema})}

View file

@ -1,12 +1,7 @@
"""Provides helpers for Z-Wave JS device automations."""
from __future__ import annotations
from typing import cast
import voluptuous as vol
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import ConfigurationValueType
from zwave_js_server.model.node import Node
from zwave_js_server.model.value import ConfigurationValue
from homeassistant.config_entries import ConfigEntryState
@ -23,24 +18,6 @@ CONF_VALUE_ID = "value_id"
VALUE_ID_REGEX = r"([0-9]+-[0-9]+-[0-9]+-).+"
def get_config_parameter_value_schema(node: Node, value_id: str) -> vol.Schema | None:
"""Get the extra fields schema for a config parameter value."""
config_value = cast(ConfigurationValue, node.values[value_id])
min_ = config_value.metadata.min
max_ = config_value.metadata.max
if config_value.configuration_value_type in (
ConfigurationValueType.RANGE,
ConfigurationValueType.MANUAL_ENTRY,
):
return vol.All(vol.Coerce(int), vol.Range(min=min_, max=max_))
if config_value.configuration_value_type == ConfigurationValueType.ENUMERATED:
return vol.In({int(k): v for k, v in config_value.metadata.states.items()})
return None
def generate_config_parameter_subtype(config_value: ConfigurationValue) -> str:
"""Generate the config parameter name used in a device automation subtype."""
parameter = str(config_value.property_)

View file

@ -31,11 +31,11 @@ from .device_automation_helpers import (
NODE_STATUSES,
async_bypass_dynamic_config_validation,
generate_config_parameter_subtype,
get_config_parameter_value_schema,
)
from .helpers import (
async_get_node_from_device_id,
check_type_schema_map,
get_value_state_schema,
get_zwave_value_from_config,
remove_keys_with_empty_values,
)
@ -209,7 +209,7 @@ async def async_get_condition_capabilities(
# Add additional fields to the automation trigger UI
if config[CONF_TYPE] == CONFIG_PARAMETER_TYPE:
value_id = config[CONF_VALUE_ID]
value_schema = get_config_parameter_value_schema(node, value_id)
value_schema = get_value_state_schema(node.values[value_id])
if value_schema is None:
return {}
return {"extra_fields": vol.Schema({vol.Required(ATTR_VALUE): value_schema})}

View file

@ -7,8 +7,9 @@ from typing import Any
from zwave_js_server.client import Client
from zwave_js_server.const import CommandClass
from zwave_js_server.dump import dump_msgs
from zwave_js_server.model.node import Node, NodeDataType
from zwave_js_server.model.node import Node
from zwave_js_server.model.value import ValueDataType
from zwave_js_server.util.node import dump_node_state
from homeassistant.components.diagnostics import REDACTED
from homeassistant.components.diagnostics.util import async_redact_data
@ -54,13 +55,20 @@ def optionally_redact_value_of_zwave_value(zwave_value: ValueDataType) -> ValueD
return zwave_value
def redact_node_state(node_state: NodeDataType) -> NodeDataType:
def redact_node_state(node_state: dict) -> dict:
"""Redact node state."""
redacted_state: NodeDataType = deepcopy(node_state)
redacted_state["values"] = [
optionally_redact_value_of_zwave_value(zwave_value)
for zwave_value in node_state["values"]
]
redacted_state: dict = deepcopy(node_state)
# dump_msgs returns values in a list but dump_node_state returns them in a dict
if isinstance(node_state["values"], list):
redacted_state["values"] = [
optionally_redact_value_of_zwave_value(zwave_value)
for zwave_value in node_state["values"]
]
else:
redacted_state["values"] = {
value_id: optionally_redact_value_of_zwave_value(zwave_value)
for value_id, zwave_value in node_state["values"].items()
}
return redacted_state
@ -129,8 +137,8 @@ async def async_get_config_entry_diagnostics(
handshake_msgs = msgs[:-1]
network_state = msgs[-1]
network_state["result"]["state"]["nodes"] = [
redact_node_state(async_redact_data(node, KEYS_TO_REDACT))
for node in network_state["result"]["state"]["nodes"]
redact_node_state(async_redact_data(node_data, KEYS_TO_REDACT))
for node_data in network_state["result"]["state"]["nodes"]
]
return {"messages": [*handshake_msgs, network_state]}
@ -148,7 +156,9 @@ async def async_get_device_diagnostics(
node = driver.controller.nodes[node_id]
entities = get_device_entities(hass, node, config_entry, device)
assert client.version
node_state = redact_node_state(async_redact_data(node.data, KEYS_TO_REDACT))
node_state = redact_node_state(
async_redact_data(dump_node_state(node), KEYS_TO_REDACT)
)
return {
"versionInfo": {
"driverVersion": client.version.driver_version,

View file

@ -1108,7 +1108,7 @@ def async_discover_single_value(
def async_discover_single_configuration_value(
value: ConfigurationValue,
) -> Generator[ZwaveDiscoveryInfo, None, None]:
"""Run discovery on a single ZWave configuration value and return matching schema info."""
"""Run discovery on single Z-Wave configuration value and return schema matches."""
if value.metadata.writeable and value.metadata.readable:
if value.configuration_value_type == ConfigurationValueType.ENUMERATED:
yield ZwaveDiscoveryInfo(
@ -1125,36 +1125,29 @@ def async_discover_single_configuration_value(
ConfigurationValueType.RANGE,
ConfigurationValueType.MANUAL_ENTRY,
):
if value.metadata.type == ValueType.BOOLEAN or (
value.metadata.min == 0 and value.metadata.max == 1
):
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.SWITCH,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
else:
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.NUMBER,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.NUMBER,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
elif value.configuration_value_type == ConfigurationValueType.BOOLEAN:
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.SWITCH,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
elif not value.metadata.writeable and value.metadata.readable:
if value.metadata.type == ValueType.BOOLEAN or (
value.metadata.min == 0
and value.metadata.max == 1
and not value.metadata.states
):
if value.configuration_value_type == ConfigurationValueType.BOOLEAN:
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,

View file

@ -7,7 +7,11 @@ from typing import Any
from zwave_js_server.const import NodeStatus
from zwave_js_server.exceptions import BaseZwaveJSServerError
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.value import Value as ZwaveValue, get_value_id_str
from zwave_js_server.model.value import (
SetValueResult,
Value as ZwaveValue,
get_value_id_str,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
@ -70,9 +74,9 @@ class ZWaveBaseEntity(Entity):
async def _async_poll_value(self, value_or_id: str | ZwaveValue) -> None:
"""Poll a value."""
# We log an error instead of raising an exception because this service call occurs
# in a separate task and we don't want to raise the exception in that separate task
# because it is confusing to the user.
# We log an error instead of raising an exception because this service call
# occurs in a separate task and we don't want to raise the exception in that
# separate task because it is confusing to the user.
try:
await self.info.node.async_poll_value(value_or_id)
except BaseZwaveJSServerError as err:
@ -312,7 +316,7 @@ class ZWaveBaseEntity(Entity):
new_value: Any,
options: dict | None = None,
wait_for_result: bool | None = None,
) -> bool | None:
) -> SetValueResult | None:
"""Set value on node."""
try:
return await self.info.node.async_set_value(

View file

@ -252,7 +252,7 @@ def async_get_node_from_entity_id(
entity_entry = ent_reg.async_get(entity_id)
if entity_entry is None or entity_entry.platform != DOMAIN:
raise ValueError(f"Entity {entity_id} is not a valid {DOMAIN} entity.")
raise ValueError(f"Entity {entity_id} is not a valid {DOMAIN} entity")
# Assert for mypy, safe because we know that zwave_js entities are always
# tied to a device
@ -414,9 +414,7 @@ def copy_available_params(
)
def get_value_state_schema(
value: ZwaveValue,
) -> vol.Schema | None:
def get_value_state_schema(value: ZwaveValue) -> vol.Schema | None:
"""Return device automation schema for a config entry."""
if isinstance(value, ConfigurationValue):
min_ = value.metadata.min
@ -427,6 +425,9 @@ def get_value_state_schema(
):
return vol.All(vol.Coerce(int), vol.Range(min=min_, max=max_))
if value.configuration_value_type == ConfigurationValueType.BOOLEAN:
return vol.Coerce(bool)
if value.configuration_value_type == ConfigurationValueType.ENUMERATED:
return vol.In({int(k): v for k, v in value.metadata.states.items()})

View file

@ -9,7 +9,7 @@
"iot_class": "local_push",
"loggers": ["zwave_js_server"],
"quality_scale": "platinum",
"requirements": ["pyserial==3.5", "zwave-js-server-python==0.49.0"],
"requirements": ["pyserial==3.5", "zwave-js-server-python==0.50.1"],
"usb": [
{
"vid": "0658",

View file

@ -8,7 +8,7 @@ from typing import Any
import voluptuous as vol
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import CommandClass, CommandStatus
from zwave_js_server.const import SET_VALUE_SUCCESS, CommandClass, CommandStatus
from zwave_js_server.exceptions import FailedZWaveCommand, SetValueFailed
from zwave_js_server.model.endpoint import Endpoint
from zwave_js_server.model.node import Node as ZwaveNode
@ -39,12 +39,6 @@ from .helpers import (
_LOGGER = logging.getLogger(__name__)
SET_VALUE_FAILED_EXC = SetValueFailed(
"Unable to set value, refer to "
"https://zwave-js.github.io/node-zwave-js/#/api/node?id=setvalue for "
"possible reasons"
)
def parameter_name_does_not_need_bitmask(
val: dict[str, int | str | list[str]]
@ -538,16 +532,20 @@ class ZWaveServices:
nodes_list = list(nodes)
# multiple set_values my fail so we will track the entire list
set_value_failed_nodes_list: list[ZwaveNode | Endpoint] = []
for node_, success in get_valid_responses_from_results(nodes_list, results):
if success is False:
# If we failed to set a value, add node to SetValueFailed exception list
set_value_failed_error_list: list[SetValueFailed] = []
for node_, result in get_valid_responses_from_results(nodes_list, results):
if result and result.status not in SET_VALUE_SUCCESS:
# If we failed to set a value, add node to exception list
set_value_failed_nodes_list.append(node_)
set_value_failed_error_list.append(
SetValueFailed(f"{result.status} {result.message}")
)
# Add the SetValueFailed exception to the results and the nodes to the node
# list. No-op if there are no SetValueFailed exceptions
# Add the exception to the results and the nodes to the node list. No-op if
# no set value commands failed
raise_exceptions_from_results(
(*nodes_list, *set_value_failed_nodes_list),
(*results, *([SET_VALUE_FAILED_EXC] * len(set_value_failed_nodes_list))),
(*results, *set_value_failed_error_list),
)
async def async_multicast_set_value(self, service: ServiceCall) -> None:
@ -611,7 +609,7 @@ class ZWaveServices:
new_value = str(new_value)
try:
success = await async_multicast_set_value(
result = await async_multicast_set_value(
client=client,
new_value=new_value,
value_data=value,
@ -621,10 +619,10 @@ class ZWaveServices:
except FailedZWaveCommand as err:
raise HomeAssistantError("Unable to set value via multicast") from err
if success is False:
if result.status not in SET_VALUE_SUCCESS:
raise HomeAssistantError(
"Unable to set value via multicast"
) from SetValueFailed
) from SetValueFailed(f"{result.status} {result.message}")
async def async_ping(self, service: ServiceCall) -> None:
"""Ping node(s)."""

View file

@ -2785,7 +2785,7 @@ zigpy==0.56.4
zm-py==0.5.2
# homeassistant.components.zwave_js
zwave-js-server-python==0.49.0
zwave-js-server-python==0.50.1
# homeassistant.components.zwave_me
zwave-me-ws==0.4.3

View file

@ -2049,7 +2049,7 @@ zigpy-znp==0.11.4
zigpy==0.56.4
# homeassistant.components.zwave_js
zwave-js-server-python==0.49.0
zwave-js-server-python==0.50.1
# homeassistant.components.zwave_me
zwave-me-ws==0.4.3

View file

@ -687,6 +687,9 @@ def mock_client_fixture(
client.version = VersionInfo.from_message(version_state)
client.ws_server_url = "ws://test:3000/zjs"
client.async_send_command.return_value = {
"result": {"success": True, "status": 255}
}
yield client

View file

@ -270,5 +270,5 @@
}
]
},
"replaced": false
"reason": 0
}

View file

@ -276,14 +276,16 @@ async def test_subscribe_node_status(
msg = await ws_client.receive_json()
assert msg["success"]
node.data["ready"] = True
new_node_data = deepcopy(multisensor_6_state)
new_node_data["ready"] = True
event = Event(
"ready",
{
"source": "node",
"event": "ready",
"nodeId": node.node_id,
"nodeState": node.data,
"nodeState": new_node_data,
},
)
node.receive_event(event)
@ -1715,7 +1717,7 @@ async def test_remove_node(
assert len(client.async_send_command.call_args_list) == 1
assert client.async_send_command.call_args[0][0] == {
"command": "controller.begin_exclusion",
"strategy": 0,
"options": {"strategy": 0},
}
# Test FailedZWaveCommand is caught

View file

@ -731,6 +731,8 @@ async def test_thermostat_raise_repair_issue_and_warning_when_setting_dry_preset
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test raise of repair issue and warning when setting Dry preset."""
client.async_send_command.return_value = {"result": {"status": 1}}
state = hass.states.get(CLIMATE_AIDOO_HVAC_UNIT_ENTITY)
assert state
@ -765,6 +767,7 @@ async def test_thermostat_raise_repair_issue_and_warning_when_setting_fan_preset
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test raise of repair issue and warning when setting Fan preset."""
client.async_send_command.return_value = {"result": {"status": 1}}
state = hass.states.get(CLIMATE_AIDOO_HVAC_UNIT_ENTITY)
assert state

View file

@ -126,6 +126,7 @@ async def test_window_cover(
assert args["value"]
client.async_send_command.reset_mock()
# Test stop after opening
await hass.services.async_call(
DOMAIN,
@ -265,6 +266,7 @@ async def test_fibaro_fgr222_shutter_cover(
assert args["value"] == 99
client.async_send_command.reset_mock()
# Test closing tilts
await hass.services.async_call(
DOMAIN,
@ -286,6 +288,7 @@ async def test_fibaro_fgr222_shutter_cover(
assert args["value"] == 0
client.async_send_command.reset_mock()
# Test setting tilt position
await hass.services.async_call(
DOMAIN,
@ -365,6 +368,7 @@ async def test_aeotec_nano_shutter_cover(
assert args["value"]
client.async_send_command.reset_mock()
# Test stop after opening
await hass.services.async_call(
DOMAIN,

View file

@ -125,7 +125,13 @@ async def test_device_diagnostics(
entity["entity_id"] == "test.unrelated_entity"
for entity in diagnostics_data["entities"]
)
assert diagnostics_data["state"] == multisensor_6.data
assert diagnostics_data["state"] == {
**multisensor_6.data,
"values": {id: val.data for id, val in multisensor_6.values.items()},
"endpoints": {
str(idx): endpoint.data for idx, endpoint in multisensor_6.endpoints.items()
},
}
async def test_device_diagnostics_error(hass: HomeAssistant, integration) -> None:
@ -230,7 +236,11 @@ async def test_device_diagnostics_secret_value(
"""Find ultraviolet property value in data."""
return next(
val
for val in data["values"]
for val in (
data["values"]
if isinstance(data["values"], list)
else data["values"].values()
)
if val["commandClass"] == CommandClass.SENSOR_MULTILEVEL
and val["property"] == PROPERTY_ULTRAVIOLET
)

View file

@ -171,6 +171,7 @@ async def test_zooz_zen72(
state = hass.states.get(entity_id)
assert state
assert state.state == STATE_UNKNOWN
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
@ -256,6 +257,7 @@ async def test_indicator_test(
state = hass.states.get(entity_id)
assert state
assert state.state == STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,

View file

@ -231,6 +231,7 @@ async def test_configurable_speeds_fan(
async def get_zwave_speed_from_percentage(percentage):
"""Set the fan to a particular percentage and get the resulting Zwave speed."""
client.async_send_command.reset_mock()
await hass.services.async_call(
"fan",
"turn_on",
@ -356,6 +357,7 @@ async def test_ge_12730_fan(hass: HomeAssistant, client, ge_12730, integration)
async def get_zwave_speed_from_percentage(percentage):
"""Set the fan to a particular percentage and get the resulting Zwave speed."""
client.async_send_command.reset_mock()
await hass.services.async_call(
"fan",
"turn_on",
@ -448,6 +450,7 @@ async def test_inovelli_lzw36(
async def get_zwave_speed_from_percentage(percentage):
"""Set the fan to a particular percentage and get the resulting Zwave speed."""
client.async_send_command.reset_mock()
await hass.services.async_call(
"fan",
"turn_on",
@ -518,6 +521,7 @@ async def test_inovelli_lzw36(
assert state.attributes[ATTR_PERCENTAGE] is None
client.async_send_command.reset_mock()
await hass.services.async_call(
"fan",
"turn_on",
@ -553,6 +557,7 @@ async def test_leviton_zw4sf_fan(
async def get_zwave_speed_from_percentage(percentage):
"""Set the fan to a particular percentage and get the resulting Zwave speed."""
client.async_send_command.reset_mock()
await hass.services.async_call(
"fan",
"turn_on",
@ -951,6 +956,7 @@ async def test_honeywell_39358_fan(
async def get_zwave_speed_from_percentage(percentage):
"""Set the fan to a particular percentage and get the resulting Zwave speed."""
client.async_send_command.reset_mock()
await hass.services.async_call(
"fan",
"turn_on",

View file

@ -1,7 +1,10 @@
"""Test the Z-Wave JS helpers module."""
import voluptuous as vol
from homeassistant.components.zwave_js.helpers import (
async_get_node_status_sensor_entity_id,
async_get_nodes_from_area_id,
get_value_state_schema,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import area_registry as ar, device_registry as dr
@ -22,3 +25,14 @@ async def test_async_get_nodes_from_area_id(hass: HomeAssistant) -> None:
area_reg = ar.async_get(hass)
area = area_reg.async_create("test")
assert not async_get_nodes_from_area_id(hass, area.id)
async def test_get_value_state_schema_boolean_config_value(
hass: HomeAssistant, client, aeon_smart_switch_6
) -> None:
"""Test get_value_state_schema for boolean config value."""
schema_validator = get_value_state_schema(
aeon_smart_switch_6.values["102-112-0-255"]
)
assert isinstance(schema_validator, vol.Coerce)
assert schema_validator.type == bool

View file

@ -1005,7 +1005,7 @@ async def test_node_removed(
event = {
"source": "controller",
"event": "node added",
"node": node.data,
"node": multisensor_6_state,
"result": {},
}
@ -1014,7 +1014,7 @@ async def test_node_removed(
old_device = dev_reg.async_get_device(identifiers={(DOMAIN, device_id)})
assert old_device.id
event = {"node": node, "replaced": False}
event = {"node": node, "reason": 0}
client.driver.controller.emit("node removed", event)
await hass.async_block_till_done()
@ -1047,14 +1047,14 @@ async def test_replace_same_node(
assert hass.states.get(AIR_TEMPERATURE_SENSOR)
# A replace node event has the extra field "replaced" set to True
# A replace node event has the extra field "reason"
# to distinguish it from an exclusion
event = Event(
type="node removed",
data={
"source": "controller",
"event": "node removed",
"replaced": True,
"reason": 3,
"node": multisensor_6_state,
},
)
@ -1139,8 +1139,8 @@ async def test_replace_different_node(
"""Test when a node is replaced with a different node."""
dev_reg = dr.async_get(hass)
node_id = multisensor_6.node_id
hank_binary_switch_state = deepcopy(hank_binary_switch_state)
hank_binary_switch_state["nodeId"] = node_id
state = deepcopy(hank_binary_switch_state)
state["nodeId"] = node_id
device_id = f"{client.driver.controller.home_id}-{node_id}"
multisensor_6_device_id = (
@ -1148,9 +1148,9 @@ async def test_replace_different_node(
f"{multisensor_6.product_type}:{multisensor_6.product_id}"
)
hank_device_id = (
f"{device_id}-{hank_binary_switch_state['manufacturerId']}:"
f"{hank_binary_switch_state['productType']}:"
f"{hank_binary_switch_state['productId']}"
f"{device_id}-{state['manufacturerId']}:"
f"{state['productType']}:"
f"{state['productId']}"
)
device = dev_reg.async_get_device(identifiers={(DOMAIN, device_id)})
@ -1171,7 +1171,7 @@ async def test_replace_different_node(
data={
"source": "controller",
"event": "node removed",
"replaced": True,
"reason": 3,
"node": multisensor_6_state,
},
)
@ -1228,7 +1228,7 @@ async def test_replace_different_node(
"source": "node",
"event": "ready",
"nodeId": node_id,
"nodeState": hank_binary_switch_state,
"nodeState": state,
},
)
client.driver.receive_event(event)
@ -1345,7 +1345,7 @@ async def test_disabled_node_status_entity_on_node_replaced(
data={
"source": "controller",
"event": "node removed",
"replaced": True,
"reason": 3,
"node": zp3111_state,
},
)

View file

@ -414,6 +414,7 @@ async def test_bulk_set_config_parameters(
identifiers={get_device_id(client.driver, multisensor_6)}
)
assert device
# Test setting config parameter by property and property_key
await hass.services.async_call(
DOMAIN,
@ -875,7 +876,9 @@ async def test_set_value(
client.async_send_command.reset_mock()
# Test that when a command fails we raise an exception
client.async_send_command.return_value = {"success": False}
client.async_send_command.return_value = {
"result": {"status": 2, "message": "test"}
}
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
@ -924,7 +927,6 @@ async def test_set_value_string(
hass: HomeAssistant, client, climate_danfoss_lc_13, lock_schlage_be469, integration
) -> None:
"""Test set_value service converts number to string when needed."""
client.async_send_command.return_value = {"success": True}
# Test that number gets converted to a string when needed
await hass.services.async_call(
@ -1240,7 +1242,9 @@ async def test_multicast_set_value(
)
# Test that when a command is unsuccessful we raise an exception
client.async_send_command.return_value = {"success": False}
client.async_send_command.return_value = {
"result": {"status": 2, "message": "test"}
}
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
@ -1381,7 +1385,7 @@ async def test_multicast_set_value_string(
integration,
) -> None:
"""Test multicast_set_value service converts number to string when needed."""
client.async_send_command.return_value = {"success": True}
client.async_send_command.return_value = {"result": {"status": 255}}
# Test that number gets converted to a string when needed
await hass.services.async_call(

View file

@ -63,6 +63,8 @@ async def test_switch(
state = hass.states.get(SWITCH_ENTITY)
assert state.state == "on"
client.async_send_command.reset_mock()
# Test turning off
await hass.services.async_call(
"switch", "turn_off", {"entity_id": SWITCH_ENTITY}, blocking=True

View file

@ -1158,7 +1158,7 @@ async def test_server_reconnect_event(
data={
"source": "controller",
"event": "node removed",
"replaced": False,
"reason": 0,
"node": lock_schlage_be469_state,
},
)
@ -1238,7 +1238,7 @@ async def test_server_reconnect_value_updated(
data={
"source": "controller",
"event": "node removed",
"replaced": False,
"reason": 0,
"node": lock_schlage_be469_state,
},
)

View file

@ -264,6 +264,8 @@ async def test_update_entity_ha_not_running(
"""Test update occurs only after HA is running."""
await hass.async_stop()
client.async_send_command.return_value = {"updates": []}
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
@ -341,7 +343,9 @@ async def test_update_entity_progress(
assert attrs[ATTR_LATEST_VERSION] == "11.2.4"
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": False}
client.async_send_command.return_value = {
"result": {"status": 2, "success": False, "reInterview": False}
}
# Test successful install call without a version
install_task = hass.async_create_task(
@ -437,7 +441,9 @@ async def test_update_entity_install_failed(
assert attrs[ATTR_LATEST_VERSION] == "11.2.4"
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"success": False}
client.async_send_command.return_value = {
"result": {"status": 2, "success": False, "reInterview": False}
}
# Test install call - we expect it to finish fail
install_task = hass.async_create_task(
@ -577,6 +583,7 @@ async def test_update_entity_delay(
) -> None:
"""Test update occurs on a delay after HA starts."""
client.async_send_command.reset_mock()
client.async_send_command.return_value = {"updates": []}
await hass.async_stop()
entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
@ -710,7 +717,9 @@ async def test_update_entity_full_restore_data_update_available(
assert state.attributes[ATTR_SKIPPED_VERSION] is None
assert state.attributes[ATTR_LATEST_VERSION] == "11.2.4"
client.async_send_command.return_value = {"success": True}
client.async_send_command.return_value = {
"result": {"status": 255, "success": True, "reInterview": False}
}
# Test successful install call without a version
install_task = hass.async_create_task(