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 code
pull/104283/head^2
Øyvind Matheson Wergeland 2023-11-20 22:38:16 +01:00 committed by GitHub
parent 5fe5057b15
commit 5527cbd78a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 111 additions and 22 deletions

View File

@ -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:

View File

@ -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, {}))