"""Tests for zlock_slots service handlers.""" import pytest from unittest.mock import patch, AsyncMock from homeassistant.exceptions import ServiceValidationError from custom_components.zlock_slots.coordinator import ZLockCoordinator from custom_components.zlock_slots.services import register_services, unregister_services from custom_components.zlock_slots.const import DOMAIN from tests.conftest import LOCK_ENTITY_ID, FakeUserData, FakeNode _ZWAVE_HELPER = "homeassistant.components.zwave_js.helpers.async_get_node_from_entity_id" @pytest.fixture async def service_env(hass, config_entry, mock_node): """Coordinator wired into hass.data with services registered and mock zwave_js services ready.""" config_entry.add_to_hass(hass) with patch(_ZWAVE_HELPER, return_value=mock_node): coord = ZLockCoordinator(hass, config_entry) await coord.async_setup() hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = coord set_lock_usercode = AsyncMock() clear_lock_usercode = AsyncMock() hass.services.async_register("zwave_js", "set_lock_usercode", set_lock_usercode) hass.services.async_register("zwave_js", "clear_lock_usercode", clear_lock_usercode) register_services(hass) yield coord, set_lock_usercode, clear_lock_usercode unregister_services(hass) coord.async_teardown() # --------------------------------------------------------------------------- # set_code # --------------------------------------------------------------------------- async def test_set_code_calls_zwave_service(hass, service_env, mock_node): coord, set_lock_usercode, _ = service_env with patch(_ZWAVE_HELPER, return_value=mock_node): await hass.services.async_call( DOMAIN, "set_code", {"lock_entity_id": LOCK_ENTITY_ID, "slot": 5, "code": "1234"}, blocking=True, ) set_lock_usercode.assert_awaited_once() service_data = set_lock_usercode.call_args[0][0].data assert service_data["code_slot"] == 5 assert service_data["usercode"] == "1234" assert service_data["entity_id"] == LOCK_ENTITY_ID async def test_set_code_auto_assigns_lowest_free_slot(hass, service_env, mock_node): """Slots 1 and 2 occupied → auto-assign picks 3.""" coord, set_lock_usercode, _ = service_env with patch(_ZWAVE_HELPER, return_value=mock_node): await hass.services.async_call( DOMAIN, "set_code", {"lock_entity_id": LOCK_ENTITY_ID, "code": "5678"}, blocking=True, ) service_data = set_lock_usercode.call_args[0][0].data assert service_data["code_slot"] == 3 async def test_set_code_auto_assign_skips_occupied(hass, service_env, mock_node): """If slot 3 is also occupied, auto-assign picks 4.""" node_with_3 = FakeNode(users=[ FakeUserData(1), FakeUserData(2), FakeUserData(3), ]) coord, set_lock_usercode, _ = service_env # Refresh to get node_with_3 slots into coordinator state. with patch(_ZWAVE_HELPER, return_value=node_with_3): await coord.async_refresh_lock(LOCK_ENTITY_ID) await hass.services.async_call( DOMAIN, "set_code", {"lock_entity_id": LOCK_ENTITY_ID, "code": "9999"}, blocking=True, ) service_data = set_lock_usercode.call_args[0][0].data assert service_data["code_slot"] == 4 async def test_set_code_with_name_saves_name(hass, service_env, mock_node): coord, _, _ = service_env with patch(_ZWAVE_HELPER, return_value=mock_node): await hass.services.async_call( DOMAIN, "set_code", {"lock_entity_id": LOCK_ENTITY_ID, "slot": 5, "code": "4321", "name": "Charlie"}, blocking=True, ) assert coord.get_name(LOCK_ENTITY_ID, 5) == "Charlie" async def test_set_code_unknown_lock_raises(hass, service_env): with pytest.raises(ServiceValidationError): await hass.services.async_call( DOMAIN, "set_code", {"lock_entity_id": "lock.unknown", "slot": 1, "code": "1234"}, blocking=True, ) # --------------------------------------------------------------------------- # clear_code # --------------------------------------------------------------------------- async def test_clear_code_calls_zwave_service(hass, service_env, mock_node): _, _, clear_lock_usercode = service_env with patch(_ZWAVE_HELPER, return_value=mock_node): await hass.services.async_call( DOMAIN, "clear_code", {"lock_entity_id": LOCK_ENTITY_ID, "slot": 2}, blocking=True, ) clear_lock_usercode.assert_awaited_once() service_data = clear_lock_usercode.call_args[0][0].data assert service_data["code_slot"] == 2 assert service_data["entity_id"] == LOCK_ENTITY_ID # --------------------------------------------------------------------------- # set_name # --------------------------------------------------------------------------- async def test_set_name_updates_coordinator(hass, service_env): coord, _, _ = service_env await hass.services.async_call( DOMAIN, "set_name", {"lock_entity_id": LOCK_ENTITY_ID, "slot": 1, "name": "Diana"}, blocking=True, ) assert coord.get_name(LOCK_ENTITY_ID, 1) == "Diana" # --------------------------------------------------------------------------- # set_slot_enabled # --------------------------------------------------------------------------- async def test_set_slot_enabled_calls_node(hass, service_env, mock_node): with patch(_ZWAVE_HELPER, return_value=mock_node): await hass.services.async_call( DOMAIN, "set_slot_enabled", {"lock_entity_id": LOCK_ENTITY_ID, "slot": 1, "enabled": False}, blocking=True, ) mock_node.access_control.set_user.assert_awaited_once() slot_arg, opts_arg = mock_node.access_control.set_user.call_args[0] assert slot_arg == 1 assert opts_arg.active is False async def test_set_slot_enabled_true(hass, service_env, mock_node): with patch(_ZWAVE_HELPER, return_value=mock_node): await hass.services.async_call( DOMAIN, "set_slot_enabled", {"lock_entity_id": LOCK_ENTITY_ID, "slot": 2, "enabled": True}, blocking=True, ) _, opts_arg = mock_node.access_control.set_user.call_args[0] assert opts_arg.active is True