Reduce missed coverage in zwave_js (#79571)

* Reduce missed coverage in zwave_js.climate and cover

* Add switch platform coverage

* Add select platform

* Add lock platform

* Remove one line of coverage from number platform

* update docstring
This commit is contained in:
Raman Gupta 2022-10-18 04:06:29 -04:00 committed by GitHub
parent c717fd19de
commit d4c28e04e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 160 additions and 28 deletions

View file

@ -188,7 +188,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
) )
self._set_modes_and_presets() self._set_modes_and_presets()
self._attr_supported_features = 0 self._attr_supported_features = 0
if len(self._hvac_presets) > 1: if self._current_mode and len(self._hvac_presets) > 1:
self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
# If any setpoint value exists, we can assume temperature # If any setpoint value exists, we can assume temperature
# can be set # can be set
@ -428,9 +428,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
async def async_set_fan_mode(self, fan_mode: str) -> None: async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode.""" """Set new target fan mode."""
if not self._fan_mode: assert self._fan_mode is not None
return
try: try:
new_state = int( new_state = int(
next( next(
@ -484,9 +482,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
async def async_set_preset_mode(self, preset_mode: str) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new target preset mode.""" """Set new target preset mode."""
if self._current_mode is None: assert self._current_mode is not None
# Thermostat(valve) has no support for setting a mode, so we make it a no-op
return
if preset_mode == PRESET_NONE: if preset_mode == PRESET_NONE:
# try to restore to the (translated) main hvac mode # try to restore to the (translated) main hvac mode
await self.async_set_hvac_mode(self.hvac_mode) await self.async_set_hvac_mode(self.hvac_mode)

View file

@ -27,7 +27,6 @@ from homeassistant.components.cover import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -138,8 +137,7 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity):
async def async_set_cover_position(self, **kwargs: Any) -> None: async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY)
if target_value is None: assert target_value is not None
raise HomeAssistantError("Missing target value on device.")
await self.info.node.async_set_value( await self.info.node.async_set_value(
target_value, percent_to_zwave_position(kwargs[ATTR_POSITION]) target_value, percent_to_zwave_position(kwargs[ATTR_POSITION])
) )
@ -147,15 +145,13 @@ class ZWaveCover(ZWaveBaseEntity, CoverEntity):
async def async_open_cover(self, **kwargs: Any) -> None: async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover.""" """Open the cover."""
target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY)
if target_value is None: assert target_value is not None
raise HomeAssistantError("Missing target value on device.")
await self.info.node.async_set_value(target_value, 99) await self.info.node.async_set_value(target_value, 99)
async def async_close_cover(self, **kwargs: Any) -> None: async def async_close_cover(self, **kwargs: Any) -> None:
"""Close cover.""" """Close cover."""
target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY) target_value = self.get_zwave_value(TARGET_VALUE_PROPERTY)
if target_value is None: assert target_value is not None
raise HomeAssistantError("Missing target value on device.")
await self.info.node.async_set_value(target_value, 0) await self.info.node.async_set_value(target_value, 0)
async def async_stop_cover(self, **kwargs: Any) -> None: async def async_stop_cover(self, **kwargs: Any) -> None:

View file

@ -113,10 +113,7 @@ class ZwaveVolumeNumberEntity(ZWaveBaseEntity, NumberEntity):
super().__init__(config_entry, driver, info) super().__init__(config_entry, driver, info)
max_value = cast(int, self.info.primary_value.metadata.max) max_value = cast(int, self.info.primary_value.metadata.max)
min_value = cast(int, self.info.primary_value.metadata.min) min_value = cast(int, self.info.primary_value.metadata.min)
self.correction_factor = max_value - min_value self.correction_factor = (max_value - min_value) or 1
# Fallback in case we can't properly calculate correction factor
if self.correction_factor == 0:
self.correction_factor = 1
# Entity class attributes # Entity class attributes
self._attr_native_min_value = 0 self._attr_native_min_value = 0

View file

@ -11,7 +11,6 @@ from zwave_js_server.model.driver import Driver
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN, SelectEntity from homeassistant.components.select import DOMAIN as SELECT_DOMAIN, SelectEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -173,7 +172,6 @@ class ZwaveMultilevelSwitchSelectEntity(ZWaveBaseEntity, SelectEntity):
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
"""Change the selected option.""" """Change the selected option."""
if (target_value := self._target_value) is None: assert self._target_value is not None
raise HomeAssistantError("Missing target value on device.")
key = next(key for key, val in self._lookup_map.items() if val == option) key = next(key for key, val in self._lookup_map.items() if val == option)
await self.info.node.async_set_value(target_value, int(key)) await self.info.node.async_set_value(self._target_value, int(key))

View file

@ -1,5 +1,11 @@
"""Test the Z-Wave JS cover platform.""" """Test the Z-Wave JS cover platform."""
from zwave_js_server.const import (
CURRENT_STATE_PROPERTY,
CURRENT_VALUE_PROPERTY,
CommandClass,
)
from zwave_js_server.event import Event from zwave_js_server.event import Event
from zwave_js_server.model.node import Node
from homeassistant.components.cover import ( from homeassistant.components.cover import (
ATTR_CURRENT_POSITION, ATTR_CURRENT_POSITION,
@ -9,6 +15,7 @@ from homeassistant.components.cover import (
SERVICE_OPEN_COVER, SERVICE_OPEN_COVER,
CoverDeviceClass, CoverDeviceClass,
) )
from homeassistant.components.zwave_js.helpers import ZwaveValueMatcher
from homeassistant.const import ( from homeassistant.const import (
ATTR_DEVICE_CLASS, ATTR_DEVICE_CLASS,
STATE_CLOSED, STATE_CLOSED,
@ -18,6 +25,8 @@ from homeassistant.const import (
STATE_UNKNOWN, STATE_UNKNOWN,
) )
from .common import replace_value_of_zwave_value
WINDOW_COVER_ENTITY = "cover.zws_12" WINDOW_COVER_ENTITY = "cover.zws_12"
GDC_COVER_ENTITY = "cover.aeon_labs_garage_door_controller_gen5" GDC_COVER_ENTITY = "cover.aeon_labs_garage_door_controller_gen5"
BLIND_COVER_ENTITY = "cover.window_blind_controller" BLIND_COVER_ENTITY = "cover.window_blind_controller"
@ -600,3 +609,59 @@ async def test_motor_barrier_cover(hass, client, gdc_zw062, integration):
state = hass.states.get(GDC_COVER_ENTITY) state = hass.states.get(GDC_COVER_ENTITY)
assert state.state == STATE_UNKNOWN assert state.state == STATE_UNKNOWN
async def test_motor_barrier_cover_no_primary_value(
hass, client, gdc_zw062_state, integration
):
"""Test the cover entity where primary value value is None."""
node_state = replace_value_of_zwave_value(
gdc_zw062_state,
[
ZwaveValueMatcher(
property_=CURRENT_STATE_PROPERTY,
command_class=CommandClass.BARRIER_OPERATOR,
)
],
None,
)
node = Node(client, node_state)
client.driver.controller.emit("node added", {"node": node})
await hass.async_block_till_done()
state = hass.states.get(GDC_COVER_ENTITY)
assert state
assert state.attributes[ATTR_DEVICE_CLASS] == CoverDeviceClass.GARAGE
assert state.state == STATE_UNKNOWN
assert ATTR_CURRENT_POSITION not in state.attributes
async def test_fibaro_FGR222_shutter_cover_no_tilt(
hass, client, fibaro_fgr222_shutter_state, integration
):
"""Test tilt function of the Fibaro Shutter devices with tilt value is None."""
node_state = replace_value_of_zwave_value(
fibaro_fgr222_shutter_state,
[
ZwaveValueMatcher(
property_="fibaro",
command_class=CommandClass.MANUFACTURER_PROPRIETARY,
property_key="venetianBlindsTilt",
),
ZwaveValueMatcher(
property_=CURRENT_VALUE_PROPERTY,
command_class=CommandClass.SWITCH_MULTILEVEL,
),
],
None,
)
node = Node(client, node_state)
client.driver.controller.emit("node added", {"node": node})
await hass.async_block_till_done()
state = hass.states.get(FIBARO_SHUTTER_COVER_ENTITY)
assert state
assert state.state == STATE_UNKNOWN
assert ATTR_CURRENT_POSITION not in state.attributes
assert ATTR_CURRENT_TILT_POSITION not in state.attributes

View file

@ -1,7 +1,12 @@
"""Test the Z-Wave JS lock platform.""" """Test the Z-Wave JS lock platform."""
from zwave_js_server.const.command_class.lock import ATTR_CODE_SLOT, ATTR_USERCODE from zwave_js_server.const import CommandClass
from zwave_js_server.const.command_class.lock import (
ATTR_CODE_SLOT,
ATTR_USERCODE,
CURRENT_MODE_PROPERTY,
)
from zwave_js_server.event import Event from zwave_js_server.event import Event
from zwave_js_server.model.node import NodeStatus from zwave_js_server.model.node import Node, NodeStatus
from homeassistant.components.lock import ( from homeassistant.components.lock import (
DOMAIN as LOCK_DOMAIN, DOMAIN as LOCK_DOMAIN,
@ -9,6 +14,7 @@ from homeassistant.components.lock import (
SERVICE_UNLOCK, SERVICE_UNLOCK,
) )
from homeassistant.components.zwave_js.const import DOMAIN as ZWAVE_JS_DOMAIN from homeassistant.components.zwave_js.const import DOMAIN as ZWAVE_JS_DOMAIN
from homeassistant.components.zwave_js.helpers import ZwaveValueMatcher
from homeassistant.components.zwave_js.lock import ( from homeassistant.components.zwave_js.lock import (
SERVICE_CLEAR_LOCK_USERCODE, SERVICE_CLEAR_LOCK_USERCODE,
SERVICE_SET_LOCK_USERCODE, SERVICE_SET_LOCK_USERCODE,
@ -17,10 +23,11 @@ from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
STATE_LOCKED, STATE_LOCKED,
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
STATE_UNKNOWN,
STATE_UNLOCKED, STATE_UNLOCKED,
) )
from .common import SCHLAGE_BE469_LOCK_ENTITY from .common import SCHLAGE_BE469_LOCK_ENTITY, replace_value_of_zwave_value
async def test_door_lock(hass, client, lock_schlage_be469, integration): async def test_door_lock(hass, client, lock_schlage_be469, integration):
@ -160,3 +167,23 @@ async def test_door_lock(hass, client, lock_schlage_be469, integration):
async def test_only_one_lock(hass, client, lock_home_connect_620, integration): async def test_only_one_lock(hass, client, lock_home_connect_620, integration):
"""Test node with both Door Lock and Lock CC values only gets one lock entity.""" """Test node with both Door Lock and Lock CC values only gets one lock entity."""
assert len(hass.states.async_entity_ids("lock")) == 1 assert len(hass.states.async_entity_ids("lock")) == 1
async def test_door_lock_no_value(hass, client, lock_schlage_be469_state, integration):
"""Test a lock entity with door lock command class that has no value for mode."""
node_state = replace_value_of_zwave_value(
lock_schlage_be469_state,
[
ZwaveValueMatcher(
property_=CURRENT_MODE_PROPERTY,
command_class=CommandClass.DOOR_LOCK,
)
],
None,
)
node = Node(client, node_state)
client.driver.controller.emit("node added", {"node": node})
await hass.async_block_till_done()
state = hass.states.get(SCHLAGE_BE469_LOCK_ENTITY)
assert state
assert state.state == STATE_UNKNOWN

View file

@ -1,15 +1,19 @@
"""Test the Z-Wave JS number platform.""" """Test the Z-Wave JS number platform."""
from unittest.mock import MagicMock from unittest.mock import MagicMock
from zwave_js_server.const import CURRENT_VALUE_PROPERTY, CommandClass
from zwave_js_server.event import Event from zwave_js_server.event import Event
from zwave_js_server.model.node import Node from zwave_js_server.model.node import Node
from homeassistant.components.zwave_js.helpers import ZwaveValueMatcher
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_UNKNOWN from homeassistant.const import STATE_UNKNOWN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity import EntityCategory
import homeassistant.helpers.entity_registry as er import homeassistant.helpers.entity_registry as er
from .common import replace_value_of_zwave_value
DEFAULT_TONE_SELECT_ENTITY = "select.indoor_siren_6_default_tone_2" DEFAULT_TONE_SELECT_ENTITY = "select.indoor_siren_6_default_tone_2"
PROTECTION_SELECT_ENTITY = "select.family_room_combo_local_protection_state" PROTECTION_SELECT_ENTITY = "select.family_room_combo_local_protection_state"
MULTILEVEL_SWITCH_SELECT_ENTITY = "select.front_door_siren" MULTILEVEL_SWITCH_SELECT_ENTITY = "select.front_door_siren"
@ -265,3 +269,27 @@ async def test_multilevel_switch_select(hass, client, fortrezz_ssa1_siren, integ
state = hass.states.get(MULTILEVEL_SWITCH_SELECT_ENTITY) state = hass.states.get(MULTILEVEL_SWITCH_SELECT_ENTITY)
assert state.state == "Strobe ONLY" assert state.state == "Strobe ONLY"
async def test_multilevel_switch_select_no_value(
hass, client, fortrezz_ssa1_siren_state, integration
):
"""Test Multilevel Switch CC based select entity with primary value is None."""
node_state = replace_value_of_zwave_value(
fortrezz_ssa1_siren_state,
[
ZwaveValueMatcher(
property_=CURRENT_VALUE_PROPERTY,
command_class=CommandClass.SWITCH_MULTILEVEL,
)
],
None,
)
node = Node(client, node_state)
client.driver.controller.emit("node added", {"node": node})
await hass.async_block_till_done()
state = hass.states.get(MULTILEVEL_SWITCH_SELECT_ENTITY)
assert state
assert state.state == STATE_UNKNOWN

View file

@ -1,11 +1,14 @@
"""Test the Z-Wave JS switch platform.""" """Test the Z-Wave JS switch platform."""
from zwave_js_server.const import CURRENT_VALUE_PROPERTY, CommandClass
from zwave_js_server.event import Event from zwave_js_server.event import Event
from zwave_js_server.model.node import Node
from homeassistant.components.switch import DOMAIN, SERVICE_TURN_OFF, SERVICE_TURN_ON from homeassistant.components.switch import DOMAIN, SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.components.zwave_js.helpers import ZwaveValueMatcher
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
from .common import SWITCH_ENTITY from .common import SWITCH_ENTITY, replace_value_of_zwave_value
async def test_switch(hass, hank_binary_switch, integration, client): async def test_switch(hass, hank_binary_switch, integration, client):
@ -14,7 +17,7 @@ async def test_switch(hass, hank_binary_switch, integration, client):
node = hank_binary_switch node = hank_binary_switch
assert state assert state
assert state.state == "off" assert state.state == STATE_OFF
# Test turning on # Test turning on
await hass.services.async_call( await hass.services.async_call(
@ -178,3 +181,25 @@ async def test_barrier_signaling_switch(hass, gdc_zw062, integration, client):
state = hass.states.get(entity) state = hass.states.get(entity)
assert state.state == STATE_ON assert state.state == STATE_ON
async def test_switch_no_value(hass, hank_binary_switch_state, integration, client):
"""Test the switch where primary value value is None."""
node_state = replace_value_of_zwave_value(
hank_binary_switch_state,
[
ZwaveValueMatcher(
property_=CURRENT_VALUE_PROPERTY,
command_class=CommandClass.SWITCH_BINARY,
)
],
None,
)
node = Node(client, node_state)
client.driver.controller.emit("node added", {"node": node})
await hass.async_block_till_done()
state = hass.states.get(SWITCH_ENTITY)
assert state
assert state.state == STATE_UNKNOWN