3
0

test_services.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. """Tests for zlock_slots service handlers."""
  2. import pytest
  3. from unittest.mock import patch, AsyncMock
  4. from homeassistant.exceptions import ServiceValidationError
  5. from custom_components.zlock_slots.coordinator import ZLockCoordinator
  6. from custom_components.zlock_slots.services import register_services, unregister_services
  7. from custom_components.zlock_slots.const import DOMAIN
  8. from tests.conftest import LOCK_ENTITY_ID, FakeUserData, FakeNode
  9. _ZWAVE_HELPER = "homeassistant.components.zwave_js.helpers.async_get_node_from_entity_id"
  10. @pytest.fixture
  11. async def service_env(hass, config_entry, mock_node):
  12. """Coordinator wired into hass.data with services registered and mock zwave_js services ready."""
  13. config_entry.add_to_hass(hass)
  14. with patch(_ZWAVE_HELPER, return_value=mock_node):
  15. coord = ZLockCoordinator(hass, config_entry)
  16. await coord.async_setup()
  17. hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = coord
  18. set_lock_usercode = AsyncMock()
  19. clear_lock_usercode = AsyncMock()
  20. hass.services.async_register("zwave_js", "set_lock_usercode", set_lock_usercode)
  21. hass.services.async_register("zwave_js", "clear_lock_usercode", clear_lock_usercode)
  22. register_services(hass)
  23. yield coord, set_lock_usercode, clear_lock_usercode
  24. unregister_services(hass)
  25. coord.async_teardown()
  26. # ---------------------------------------------------------------------------
  27. # set_code
  28. # ---------------------------------------------------------------------------
  29. async def test_set_code_calls_zwave_service(hass, service_env, mock_node):
  30. coord, set_lock_usercode, _ = service_env
  31. with patch(_ZWAVE_HELPER, return_value=mock_node):
  32. await hass.services.async_call(
  33. DOMAIN, "set_code",
  34. {"lock_entity_id": LOCK_ENTITY_ID, "slot": 5, "code": "1234"},
  35. blocking=True,
  36. )
  37. set_lock_usercode.assert_awaited_once()
  38. service_data = set_lock_usercode.call_args[0][0].data
  39. assert service_data["code_slot"] == 5
  40. assert service_data["usercode"] == "1234"
  41. assert service_data["entity_id"] == LOCK_ENTITY_ID
  42. async def test_set_code_auto_assigns_lowest_free_slot(hass, service_env, mock_node):
  43. """Slots 1 and 2 occupied → auto-assign picks 3."""
  44. coord, set_lock_usercode, _ = service_env
  45. with patch(_ZWAVE_HELPER, return_value=mock_node):
  46. await hass.services.async_call(
  47. DOMAIN, "set_code",
  48. {"lock_entity_id": LOCK_ENTITY_ID, "code": "5678"},
  49. blocking=True,
  50. )
  51. service_data = set_lock_usercode.call_args[0][0].data
  52. assert service_data["code_slot"] == 3
  53. async def test_set_code_auto_assign_skips_occupied(hass, service_env, mock_node):
  54. """If slot 3 is also occupied, auto-assign picks 4."""
  55. node_with_3 = FakeNode(users=[
  56. FakeUserData(1), FakeUserData(2), FakeUserData(3),
  57. ])
  58. coord, set_lock_usercode, _ = service_env
  59. # Refresh to get node_with_3 slots into coordinator state.
  60. with patch(_ZWAVE_HELPER, return_value=node_with_3):
  61. await coord.async_refresh_lock(LOCK_ENTITY_ID)
  62. await hass.services.async_call(
  63. DOMAIN, "set_code",
  64. {"lock_entity_id": LOCK_ENTITY_ID, "code": "9999"},
  65. blocking=True,
  66. )
  67. service_data = set_lock_usercode.call_args[0][0].data
  68. assert service_data["code_slot"] == 4
  69. async def test_set_code_with_name_saves_name(hass, service_env, mock_node):
  70. coord, _, _ = service_env
  71. with patch(_ZWAVE_HELPER, return_value=mock_node):
  72. await hass.services.async_call(
  73. DOMAIN, "set_code",
  74. {"lock_entity_id": LOCK_ENTITY_ID, "slot": 5, "code": "4321", "name": "Charlie"},
  75. blocking=True,
  76. )
  77. assert coord.get_name(LOCK_ENTITY_ID, 5) == "Charlie"
  78. async def test_set_code_unknown_lock_raises(hass, service_env):
  79. with pytest.raises(ServiceValidationError):
  80. await hass.services.async_call(
  81. DOMAIN, "set_code",
  82. {"lock_entity_id": "lock.unknown", "slot": 1, "code": "1234"},
  83. blocking=True,
  84. )
  85. # ---------------------------------------------------------------------------
  86. # clear_code
  87. # ---------------------------------------------------------------------------
  88. async def test_clear_code_calls_zwave_service(hass, service_env, mock_node):
  89. _, _, clear_lock_usercode = service_env
  90. with patch(_ZWAVE_HELPER, return_value=mock_node):
  91. await hass.services.async_call(
  92. DOMAIN, "clear_code",
  93. {"lock_entity_id": LOCK_ENTITY_ID, "slot": 2},
  94. blocking=True,
  95. )
  96. clear_lock_usercode.assert_awaited_once()
  97. service_data = clear_lock_usercode.call_args[0][0].data
  98. assert service_data["code_slot"] == 2
  99. assert service_data["entity_id"] == LOCK_ENTITY_ID
  100. # ---------------------------------------------------------------------------
  101. # set_name
  102. # ---------------------------------------------------------------------------
  103. async def test_set_name_updates_coordinator(hass, service_env):
  104. coord, _, _ = service_env
  105. await hass.services.async_call(
  106. DOMAIN, "set_name",
  107. {"lock_entity_id": LOCK_ENTITY_ID, "slot": 1, "name": "Diana"},
  108. blocking=True,
  109. )
  110. assert coord.get_name(LOCK_ENTITY_ID, 1) == "Diana"
  111. # ---------------------------------------------------------------------------
  112. # set_slot_enabled
  113. # ---------------------------------------------------------------------------
  114. async def test_set_slot_enabled_calls_node(hass, service_env, mock_node):
  115. with patch(_ZWAVE_HELPER, return_value=mock_node):
  116. await hass.services.async_call(
  117. DOMAIN, "set_slot_enabled",
  118. {"lock_entity_id": LOCK_ENTITY_ID, "slot": 1, "enabled": False},
  119. blocking=True,
  120. )
  121. mock_node.access_control.set_user.assert_awaited_once()
  122. slot_arg, opts_arg = mock_node.access_control.set_user.call_args[0]
  123. assert slot_arg == 1
  124. assert opts_arg.active is False
  125. async def test_set_slot_enabled_true(hass, service_env, mock_node):
  126. with patch(_ZWAVE_HELPER, return_value=mock_node):
  127. await hass.services.async_call(
  128. DOMAIN, "set_slot_enabled",
  129. {"lock_entity_id": LOCK_ENTITY_ID, "slot": 2, "enabled": True},
  130. blocking=True,
  131. )
  132. _, opts_arg = mock_node.access_control.set_user.call_args[0]
  133. assert opts_arg.active is True