Fix default lock code for lock services (#103463)
* verisure: Support default code from lock entity * Actually use default lock code * Typing * Only pass default code if set * Avoid passing code as empty string * Simplified codepull/104283/head^2
parent
5fe5057b15
commit
5527cbd78a
|
@ -87,40 +87,34 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
async def _async_lock(entity: LockEntity, service_call: ServiceCall) -> None:
|
||||
"""Lock the lock."""
|
||||
code: str = service_call.data.get(
|
||||
ATTR_CODE, entity._lock_option_default_code # pylint: disable=protected-access
|
||||
)
|
||||
@callback
|
||||
def _add_default_code(entity: LockEntity, service_call: ServiceCall) -> dict[Any, Any]:
|
||||
data = remove_entity_service_fields(service_call)
|
||||
code: str = data.pop(ATTR_CODE, "")
|
||||
if not code:
|
||||
code = entity._lock_option_default_code # pylint: disable=protected-access
|
||||
if entity.code_format_cmp and not entity.code_format_cmp.match(code):
|
||||
raise ValueError(
|
||||
f"Code '{code}' for locking {entity.entity_id} doesn't match pattern {entity.code_format}"
|
||||
)
|
||||
await entity.async_lock(**remove_entity_service_fields(service_call))
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
return data
|
||||
|
||||
|
||||
async def _async_lock(entity: LockEntity, service_call: ServiceCall) -> None:
|
||||
"""Lock the lock."""
|
||||
await entity.async_lock(**_add_default_code(entity, service_call))
|
||||
|
||||
|
||||
async def _async_unlock(entity: LockEntity, service_call: ServiceCall) -> None:
|
||||
"""Unlock the lock."""
|
||||
code: str = service_call.data.get(
|
||||
ATTR_CODE, entity._lock_option_default_code # pylint: disable=protected-access
|
||||
)
|
||||
if entity.code_format_cmp and not entity.code_format_cmp.match(code):
|
||||
raise ValueError(
|
||||
f"Code '{code}' for unlocking {entity.entity_id} doesn't match pattern {entity.code_format}"
|
||||
)
|
||||
await entity.async_unlock(**remove_entity_service_fields(service_call))
|
||||
await entity.async_unlock(**_add_default_code(entity, service_call))
|
||||
|
||||
|
||||
async def _async_open(entity: LockEntity, service_call: ServiceCall) -> None:
|
||||
"""Open the door latch."""
|
||||
code: str = service_call.data.get(
|
||||
ATTR_CODE, entity._lock_option_default_code # pylint: disable=protected-access
|
||||
)
|
||||
if entity.code_format_cmp and not entity.code_format_cmp.match(code):
|
||||
raise ValueError(
|
||||
f"Code '{code}' for opening {entity.entity_id} doesn't match pattern {entity.code_format}"
|
||||
)
|
||||
await entity.async_open(**remove_entity_service_fields(service_call))
|
||||
await entity.async_open(**_add_default_code(entity, service_call))
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
|
|
@ -42,6 +42,8 @@ class MockLockEntity(LockEntity):
|
|||
) -> None:
|
||||
"""Initialize mock lock entity."""
|
||||
self._attr_supported_features = supported_features
|
||||
self.calls_lock = MagicMock()
|
||||
self.calls_unlock = MagicMock()
|
||||
self.calls_open = MagicMock()
|
||||
if code_format is not None:
|
||||
self._attr_code_format = code_format
|
||||
|
@ -49,11 +51,13 @@ class MockLockEntity(LockEntity):
|
|||
|
||||
async def async_lock(self, **kwargs: Any) -> None:
|
||||
"""Lock the lock."""
|
||||
self.calls_lock(kwargs)
|
||||
self._attr_is_locking = False
|
||||
self._attr_is_locked = True
|
||||
|
||||
async def async_unlock(self, **kwargs: Any) -> None:
|
||||
"""Unlock the lock."""
|
||||
self.calls_unlock(kwargs)
|
||||
self._attr_is_unlocking = False
|
||||
self._attr_is_locked = False
|
||||
|
||||
|
@ -232,6 +236,50 @@ async def test_lock_unlock_with_code(hass: HomeAssistant) -> None:
|
|||
assert not lock.is_locked
|
||||
|
||||
|
||||
async def test_lock_with_illegal_code(hass: HomeAssistant) -> None:
|
||||
"""Test lock entity with default code that does not match the code format."""
|
||||
lock = MockLockEntity(
|
||||
code_format=r"^\d{4}$",
|
||||
supported_features=LockEntityFeature.OPEN,
|
||||
)
|
||||
lock.hass = hass
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await _async_open(
|
||||
lock, ServiceCall(DOMAIN, SERVICE_OPEN, {ATTR_CODE: "123456"})
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
await _async_lock(
|
||||
lock, ServiceCall(DOMAIN, SERVICE_LOCK, {ATTR_CODE: "123456"})
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
await _async_unlock(
|
||||
lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {ATTR_CODE: "123456"})
|
||||
)
|
||||
|
||||
|
||||
async def test_lock_with_no_code(hass: HomeAssistant) -> None:
|
||||
"""Test lock entity with default code that does not match the code format."""
|
||||
lock = MockLockEntity(
|
||||
supported_features=LockEntityFeature.OPEN,
|
||||
)
|
||||
lock.hass = hass
|
||||
|
||||
await _async_open(lock, ServiceCall(DOMAIN, SERVICE_OPEN, {}))
|
||||
lock.calls_open.assert_called_with({})
|
||||
await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {}))
|
||||
lock.calls_lock.assert_called_with({})
|
||||
await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {}))
|
||||
lock.calls_unlock.assert_called_with({})
|
||||
|
||||
await _async_open(lock, ServiceCall(DOMAIN, SERVICE_OPEN, {ATTR_CODE: ""}))
|
||||
lock.calls_open.assert_called_with({})
|
||||
await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {ATTR_CODE: ""}))
|
||||
lock.calls_lock.assert_called_with({})
|
||||
await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {ATTR_CODE: ""}))
|
||||
lock.calls_unlock.assert_called_with({})
|
||||
|
||||
|
||||
async def test_lock_with_default_code(hass: HomeAssistant) -> None:
|
||||
"""Test lock entity with default code."""
|
||||
lock = MockLockEntity(
|
||||
|
@ -245,5 +293,52 @@ async def test_lock_with_default_code(hass: HomeAssistant) -> None:
|
|||
assert lock._lock_option_default_code == "1234"
|
||||
|
||||
await _async_open(lock, ServiceCall(DOMAIN, SERVICE_OPEN, {}))
|
||||
lock.calls_open.assert_called_with({ATTR_CODE: "1234"})
|
||||
await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {}))
|
||||
lock.calls_lock.assert_called_with({ATTR_CODE: "1234"})
|
||||
await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {}))
|
||||
lock.calls_unlock.assert_called_with({ATTR_CODE: "1234"})
|
||||
|
||||
await _async_open(lock, ServiceCall(DOMAIN, SERVICE_OPEN, {ATTR_CODE: ""}))
|
||||
lock.calls_open.assert_called_with({ATTR_CODE: "1234"})
|
||||
await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {ATTR_CODE: ""}))
|
||||
lock.calls_lock.assert_called_with({ATTR_CODE: "1234"})
|
||||
await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {ATTR_CODE: ""}))
|
||||
lock.calls_unlock.assert_called_with({ATTR_CODE: "1234"})
|
||||
|
||||
|
||||
async def test_lock_with_provided_and_default_code(hass: HomeAssistant) -> None:
|
||||
"""Test lock entity with provided code when default code is set."""
|
||||
lock = MockLockEntity(
|
||||
code_format=r"^\d{4}$",
|
||||
supported_features=LockEntityFeature.OPEN,
|
||||
lock_option_default_code="1234",
|
||||
)
|
||||
lock.hass = hass
|
||||
|
||||
await _async_open(lock, ServiceCall(DOMAIN, SERVICE_OPEN, {ATTR_CODE: "4321"}))
|
||||
lock.calls_open.assert_called_with({ATTR_CODE: "4321"})
|
||||
await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {ATTR_CODE: "4321"}))
|
||||
lock.calls_lock.assert_called_with({ATTR_CODE: "4321"})
|
||||
await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {ATTR_CODE: "4321"}))
|
||||
lock.calls_unlock.assert_called_with({ATTR_CODE: "4321"})
|
||||
|
||||
|
||||
async def test_lock_with_illegal_default_code(hass: HomeAssistant) -> None:
|
||||
"""Test lock entity with default code that does not match the code format."""
|
||||
lock = MockLockEntity(
|
||||
code_format=r"^\d{4}$",
|
||||
supported_features=LockEntityFeature.OPEN,
|
||||
lock_option_default_code="123456",
|
||||
)
|
||||
lock.hass = hass
|
||||
|
||||
assert lock.state_attributes == {"code_format": r"^\d{4}$"}
|
||||
assert lock._lock_option_default_code == "123456"
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await _async_open(lock, ServiceCall(DOMAIN, SERVICE_OPEN, {}))
|
||||
with pytest.raises(ValueError):
|
||||
await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {}))
|
||||
with pytest.raises(ValueError):
|
||||
await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {}))
|
||||
|
|
Loading…
Reference in New Issue