"""The tests for the lock component.""" from __future__ import annotations from typing import Any from unittest.mock import MagicMock import pytest from homeassistant.components.lock import ( ATTR_CODE, CONF_DEFAULT_CODE, DOMAIN, SERVICE_LOCK, SERVICE_OPEN, SERVICE_UNLOCK, STATE_JAMMED, STATE_LOCKED, STATE_LOCKING, STATE_UNLOCKED, STATE_UNLOCKING, LockEntity, LockEntityFeature, _async_lock, _async_open, _async_unlock, ) from homeassistant.core import HomeAssistant, ServiceCall import homeassistant.helpers.entity_registry as er from homeassistant.setup import async_setup_component from tests.testing_config.custom_components.test.lock import MockLock class MockLockEntity(LockEntity): """Mock lock to use in tests.""" def __init__( self, code_format: str | None = None, lock_option_default_code: str = "", supported_features: LockEntityFeature = LockEntityFeature(0), ) -> 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 self._lock_option_default_code = lock_option_default_code 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 async def async_open(self, **kwargs: Any) -> None: """Open the door latch.""" self.calls_open(kwargs) async def test_lock_default(hass: HomeAssistant) -> None: """Test lock entity with defaults.""" lock = MockLockEntity() lock.hass = hass assert lock.code_format is None assert lock.state is None async def test_lock_states(hass: HomeAssistant) -> None: """Test lock entity states.""" lock = MockLockEntity() lock.hass = hass assert lock.state is None lock._attr_is_locking = True assert lock.is_locking assert lock.state == STATE_LOCKING await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {})) assert lock.is_locked assert lock.state == STATE_LOCKED lock._attr_is_unlocking = True assert lock.is_unlocking assert lock.state == STATE_UNLOCKING await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {})) assert not lock.is_locked assert lock.state == STATE_UNLOCKED lock._attr_is_jammed = True assert lock.is_jammed assert lock.state == STATE_JAMMED assert not lock.is_locked async def test_set_default_code_option( hass: HomeAssistant, entity_registry: er.EntityRegistry, enable_custom_integrations: None, ) -> None: """Test default code stored in the registry.""" entry = entity_registry.async_get_or_create("lock", "test", "very_unique") await hass.async_block_till_done() platform = getattr(hass.components, "test.lock") platform.init(empty=True) platform.ENTITIES["lock1"] = platform.MockLock( name="Test", code_format=r"^\d{4}$", supported_features=LockEntityFeature.OPEN, unique_id="very_unique", ) assert await async_setup_component(hass, "lock", {"lock": {"platform": "test"}}) await hass.async_block_till_done() entity0: MockLock = platform.ENTITIES["lock1"] entity_registry.async_update_entity_options( entry.entity_id, "lock", {CONF_DEFAULT_CODE: "1234"} ) await hass.async_block_till_done() assert entity0._lock_option_default_code == "1234" async def test_default_code_option_update( hass: HomeAssistant, entity_registry: er.EntityRegistry, enable_custom_integrations: None, ) -> None: """Test default code stored in the registry is updated.""" entry = entity_registry.async_get_or_create("lock", "test", "very_unique") await hass.async_block_till_done() platform = getattr(hass.components, "test.lock") platform.init(empty=True) # Pre-register entities entry = entity_registry.async_get_or_create("lock", "test", "very_unique") entity_registry.async_update_entity_options( entry.entity_id, "lock", { "default_code": "5432", }, ) platform.ENTITIES["lock1"] = platform.MockLock( name="Test", code_format=r"^\d{4}$", supported_features=LockEntityFeature.OPEN, unique_id="very_unique", ) assert await async_setup_component(hass, "lock", {"lock": {"platform": "test"}}) await hass.async_block_till_done() entity0: MockLock = platform.ENTITIES["lock1"] assert entity0._lock_option_default_code == "5432" entity_registry.async_update_entity_options( entry.entity_id, "lock", {CONF_DEFAULT_CODE: "1234"} ) await hass.async_block_till_done() assert entity0._lock_option_default_code == "1234" async def test_lock_open_with_code(hass: HomeAssistant) -> None: """Test lock entity with open service.""" lock = MockLockEntity( code_format=r"^\d{4}$", supported_features=LockEntityFeature.OPEN ) lock.hass = hass assert lock.state_attributes == {"code_format": r"^\d{4}$"} with pytest.raises(ValueError): await _async_open(lock, ServiceCall(DOMAIN, SERVICE_OPEN, {})) with pytest.raises(ValueError): await _async_open(lock, ServiceCall(DOMAIN, SERVICE_OPEN, {ATTR_CODE: ""})) with pytest.raises(ValueError): await _async_open(lock, ServiceCall(DOMAIN, SERVICE_OPEN, {ATTR_CODE: "HELLO"})) await _async_open(lock, ServiceCall(DOMAIN, SERVICE_OPEN, {ATTR_CODE: "1234"})) assert lock.calls_open.call_count == 1 async def test_lock_lock_with_code(hass: HomeAssistant) -> None: """Test lock entity with open service.""" lock = MockLockEntity(code_format=r"^\d{4}$") lock.hass = hass await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {ATTR_CODE: "1234"})) assert not lock.is_locked with pytest.raises(ValueError): await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {})) with pytest.raises(ValueError): await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {ATTR_CODE: ""})) with pytest.raises(ValueError): await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {ATTR_CODE: "HELLO"})) await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_LOCK, {ATTR_CODE: "1234"})) assert lock.is_locked async def test_lock_unlock_with_code(hass: HomeAssistant) -> None: """Test unlock entity with open service.""" lock = MockLockEntity(code_format=r"^\d{4}$") lock.hass = hass await _async_lock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {ATTR_CODE: "1234"})) assert lock.is_locked with pytest.raises(ValueError): await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {})) with pytest.raises(ValueError): await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {ATTR_CODE: ""})) with pytest.raises(ValueError): await _async_unlock( lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {ATTR_CODE: "HELLO"}) ) await _async_unlock(lock, ServiceCall(DOMAIN, SERVICE_UNLOCK, {ATTR_CODE: "1234"})) 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( code_format=r"^\d{4}$", supported_features=LockEntityFeature.OPEN, lock_option_default_code="1234", ) lock.hass = hass assert lock.state_attributes == {"code_format": r"^\d{4}$"} 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, {}))