3
0

test_sensor.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. """Tests for sensor entities: native_value, extra_state_attributes, device attachment."""
  2. import pytest
  3. from unittest.mock import patch
  4. from homeassistant.helpers import device_registry as dr, entity_registry as er
  5. from pytest_homeassistant_custom_component.common import MockConfigEntry
  6. from custom_components.zlock_slots.coordinator import ZLockCoordinator
  7. from custom_components.zlock_slots.sensor import (
  8. ZLockLastActionSensor,
  9. ZLockLastUserSensor,
  10. ZLockSlotsSensor,
  11. ZLockGroupLastActionSensor,
  12. ZLockGroupLastUserSensor,
  13. )
  14. from custom_components.zlock_slots.const import (
  15. DOMAIN, CONF_GROUPS, CONF_GROUP_NAME, CONF_LOCK_ENTITY_IDS,
  16. )
  17. from tests.conftest import LOCK_ENTITY_ID, ALARM_TYPE_ENTITY, ALARM_LEVEL_ENTITY
  18. _ZWAVE_HELPER = "homeassistant.components.zwave_js.helpers.async_get_node_from_entity_id"
  19. @pytest.fixture
  20. async def coord(hass, config_entry, mock_node):
  21. config_entry.add_to_hass(hass)
  22. with patch(_ZWAVE_HELPER, return_value=mock_node):
  23. c = ZLockCoordinator(hass, config_entry)
  24. await c.async_setup()
  25. await hass.async_block_till_done()
  26. yield c
  27. c.async_teardown()
  28. # ---------------------------------------------------------------------------
  29. # ZLockLastActionSensor
  30. # ---------------------------------------------------------------------------
  31. async def test_last_action_none_initially(coord, config_entry):
  32. sensor = ZLockLastActionSensor(coord, config_entry, LOCK_ENTITY_ID)
  33. assert sensor.native_value is None
  34. async def test_last_action_after_event(hass, coord, config_entry):
  35. hass.states.async_set(ALARM_LEVEL_ENTITY, "1")
  36. hass.states.async_set(ALARM_TYPE_ENTITY, "18")
  37. await hass.async_block_till_done()
  38. sensor = ZLockLastActionSensor(coord, config_entry, LOCK_ENTITY_ID)
  39. assert sensor.native_value == "Locked"
  40. # ---------------------------------------------------------------------------
  41. # ZLockLastUserSensor
  42. # ---------------------------------------------------------------------------
  43. async def test_last_user_none_initially(coord, config_entry):
  44. sensor = ZLockLastUserSensor(coord, config_entry, LOCK_ENTITY_ID)
  45. assert sensor.native_value is None
  46. async def test_last_user_slot_number_when_unnamed(hass, coord, config_entry):
  47. hass.states.async_set(ALARM_LEVEL_ENTITY, "2")
  48. hass.states.async_set(ALARM_TYPE_ENTITY, "19")
  49. await hass.async_block_till_done()
  50. sensor = ZLockLastUserSensor(coord, config_entry, LOCK_ENTITY_ID)
  51. assert sensor.native_value == "2"
  52. async def test_last_user_name_when_named(hass, coord, config_entry):
  53. await coord.async_set_name(LOCK_ENTITY_ID, 1, "Eve")
  54. hass.states.async_set(ALARM_LEVEL_ENTITY, "1")
  55. hass.states.async_set(ALARM_TYPE_ENTITY, "18")
  56. await hass.async_block_till_done()
  57. sensor = ZLockLastUserSensor(coord, config_entry, LOCK_ENTITY_ID)
  58. assert sensor.native_value == "Eve"
  59. async def test_last_user_manual(hass, coord, config_entry):
  60. hass.states.async_set(ALARM_TYPE_ENTITY, "21")
  61. await hass.async_block_till_done()
  62. sensor = ZLockLastUserSensor(coord, config_entry, LOCK_ENTITY_ID)
  63. assert sensor.native_value == "<Manual>"
  64. async def test_last_user_radio(hass, coord, config_entry):
  65. hass.states.async_set(ALARM_TYPE_ENTITY, "25")
  66. await hass.async_block_till_done()
  67. sensor = ZLockLastUserSensor(coord, config_entry, LOCK_ENTITY_ID)
  68. assert sensor.native_value == "<Radio>"
  69. # ---------------------------------------------------------------------------
  70. # ZLockSlotsSensor
  71. # ---------------------------------------------------------------------------
  72. async def test_slots_sensor_count(coord, config_entry):
  73. sensor = ZLockSlotsSensor(coord, config_entry, LOCK_ENTITY_ID)
  74. assert sensor.native_value == 2
  75. async def test_slots_sensor_zero_when_empty(hass, config_entry):
  76. from tests.conftest import FakeNode
  77. config_entry.add_to_hass(hass)
  78. with patch(_ZWAVE_HELPER, return_value=FakeNode(users=[])):
  79. c = ZLockCoordinator(hass, config_entry)
  80. await c.async_setup()
  81. sensor = ZLockSlotsSensor(c, config_entry, LOCK_ENTITY_ID)
  82. assert sensor.native_value == 0
  83. assert sensor.extra_state_attributes == {"slots": []}
  84. c.async_teardown()
  85. async def test_slots_sensor_attributes_structure(coord, config_entry):
  86. sensor = ZLockSlotsSensor(coord, config_entry, LOCK_ENTITY_ID)
  87. attrs = sensor.extra_state_attributes
  88. assert "slots" in attrs
  89. slots = attrs["slots"]
  90. assert len(slots) == 2
  91. assert slots[0] == {"slot_num": 1, "status": "enabled", "name": None}
  92. assert slots[1] == {"slot_num": 2, "status": "disabled", "name": None}
  93. async def test_slots_sensor_attributes_sorted(hass, config_entry):
  94. from tests.conftest import FakeNode, FakeUserData
  95. node = FakeNode(users=[FakeUserData(5), FakeUserData(1), FakeUserData(3)])
  96. config_entry.add_to_hass(hass)
  97. with patch(_ZWAVE_HELPER, return_value=node):
  98. c = ZLockCoordinator(hass, config_entry)
  99. await c.async_setup()
  100. sensor = ZLockSlotsSensor(c, config_entry, LOCK_ENTITY_ID)
  101. slot_nums = [s["slot_num"] for s in sensor.extra_state_attributes["slots"]]
  102. assert slot_nums == [1, 3, 5]
  103. c.async_teardown()
  104. async def test_slots_sensor_attributes_include_names(hass, config_entry, mock_node):
  105. config_entry.add_to_hass(hass)
  106. with patch(_ZWAVE_HELPER, return_value=mock_node):
  107. c = ZLockCoordinator(hass, config_entry)
  108. c._names[f"{LOCK_ENTITY_ID}:1"] = "Frank"
  109. await c.async_setup()
  110. sensor = ZLockSlotsSensor(c, config_entry, LOCK_ENTITY_ID)
  111. slot1 = sensor.extra_state_attributes["slots"][0]
  112. assert slot1["name"] == "Frank"
  113. c.async_teardown()
  114. # ---------------------------------------------------------------------------
  115. # Group sensors
  116. # ---------------------------------------------------------------------------
  117. @pytest.fixture
  118. def group_config_entry():
  119. from pytest_homeassistant_custom_component.common import MockConfigEntry
  120. from custom_components.zlock_slots.const import DOMAIN
  121. return MockConfigEntry(
  122. domain=DOMAIN,
  123. data={CONF_LOCK_ENTITY_IDS: [LOCK_ENTITY_ID]},
  124. options={
  125. CONF_GROUPS: [{
  126. CONF_GROUP_NAME: "All Locks",
  127. CONF_LOCK_ENTITY_IDS: [LOCK_ENTITY_ID],
  128. }]
  129. },
  130. entry_id="test_group_entry",
  131. )
  132. async def test_group_last_action_initially_none(hass, group_config_entry, mock_node):
  133. group_config_entry.add_to_hass(hass)
  134. with patch(_ZWAVE_HELPER, return_value=mock_node):
  135. c = ZLockCoordinator(hass, group_config_entry)
  136. await c.async_setup()
  137. sensor = ZLockGroupLastActionSensor(c, group_config_entry, "All Locks")
  138. assert sensor.native_value is None
  139. c.async_teardown()
  140. async def test_group_last_action_after_alarm(hass, group_config_entry, mock_node):
  141. group_config_entry.add_to_hass(hass)
  142. with patch(_ZWAVE_HELPER, return_value=mock_node):
  143. c = ZLockCoordinator(hass, group_config_entry)
  144. await c.async_setup()
  145. hass.states.async_set(ALARM_TYPE_ENTITY, "24")
  146. await hass.async_block_till_done()
  147. sensor = ZLockGroupLastActionSensor(c, group_config_entry, "All Locks")
  148. assert sensor.native_value == "Locked"
  149. c.async_teardown()
  150. async def test_group_last_user_after_alarm(hass, group_config_entry, mock_node):
  151. group_config_entry.add_to_hass(hass)
  152. with patch(_ZWAVE_HELPER, return_value=mock_node):
  153. c = ZLockCoordinator(hass, group_config_entry)
  154. await c.async_setup()
  155. hass.states.async_set(ALARM_TYPE_ENTITY, "22")
  156. await hass.async_block_till_done()
  157. sensor = ZLockGroupLastUserSensor(c, group_config_entry, "All Locks")
  158. assert sensor.native_value == "<Manual>"
  159. c.async_teardown()
  160. # ---------------------------------------------------------------------------
  161. # Device attachment
  162. # ---------------------------------------------------------------------------
  163. def _register_lock_device(hass) -> dr.DeviceEntry:
  164. """Create a fake zwave_js device with a registered lock.front_door entity."""
  165. zwave_entry = MockConfigEntry(domain="zwave_js", entry_id="fake_zwave_entry")
  166. zwave_entry.add_to_hass(hass)
  167. device = dr.async_get(hass).async_get_or_create(
  168. config_entry_id=zwave_entry.entry_id,
  169. identifiers={("zwave_js", "3245-89")},
  170. name="Front Door",
  171. )
  172. er.async_get(hass).async_get_or_create(
  173. "lock", "zwave_js", "front_door_lock_unique",
  174. device_id=device.id,
  175. suggested_object_id="front_door",
  176. )
  177. return device
  178. async def _setup_integration(hass, entry, mock_node):
  179. entry.add_to_hass(hass)
  180. with patch(_ZWAVE_HELPER, return_value=mock_node):
  181. assert await hass.config_entries.async_setup(entry.entry_id)
  182. await hass.async_block_till_done()
  183. async def test_lock_sensors_attach_to_lock_device(hass, config_entry, mock_node):
  184. device = _register_lock_device(hass)
  185. await _setup_integration(hass, config_entry, mock_node)
  186. ent_reg = er.async_get(hass)
  187. for suffix in ("last_action", "last_user", "slots"):
  188. entity_id = ent_reg.async_get_entity_id(
  189. "sensor", DOMAIN, f"{config_entry.entry_id}_{LOCK_ENTITY_ID}_{suffix}"
  190. )
  191. assert entity_id is not None
  192. assert ent_reg.async_get(entity_id).device_id == device.id
  193. async def test_lock_sensors_without_zwave_device(hass, config_entry, mock_node):
  194. """Lock not in the entity registry — sensors still created, just device-less."""
  195. await _setup_integration(hass, config_entry, mock_node)
  196. ent_reg = er.async_get(hass)
  197. entity_id = ent_reg.async_get_entity_id(
  198. "sensor", DOMAIN, f"{config_entry.entry_id}_{LOCK_ENTITY_ID}_last_action"
  199. )
  200. assert entity_id is not None
  201. assert ent_reg.async_get(entity_id).device_id is None
  202. async def test_group_sensors_get_service_device(hass, group_config_entry, mock_node):
  203. await _setup_integration(hass, group_config_entry, mock_node)
  204. device = dr.async_get(hass).async_get_device(identifiers={(DOMAIN, "group_All Locks")})
  205. assert device is not None
  206. assert device.entry_type is dr.DeviceEntryType.SERVICE
  207. assert device.name == "All Locks"
  208. ent_reg = er.async_get(hass)
  209. entity_id = ent_reg.async_get_entity_id(
  210. "sensor", DOMAIN, f"{group_config_entry.entry_id}_group_All Locks_last_action"
  211. )
  212. assert ent_reg.async_get(entity_id).device_id == device.id
  213. async def test_stale_group_device_and_entities_removed_on_reload(
  214. hass, group_config_entry, mock_node
  215. ):
  216. await _setup_integration(hass, group_config_entry, mock_node)
  217. dev_reg = dr.async_get(hass)
  218. assert dev_reg.async_get_device(identifiers={(DOMAIN, "group_All Locks")})
  219. with patch(_ZWAVE_HELPER, return_value=mock_node):
  220. # Update listener reloads the entry; cleanup runs during sensor setup.
  221. hass.config_entries.async_update_entry(group_config_entry, options={CONF_GROUPS: []})
  222. await hass.async_block_till_done()
  223. assert dev_reg.async_get_device(identifiers={(DOMAIN, "group_All Locks")}) is None
  224. ent_reg = er.async_get(hass)
  225. assert ent_reg.async_get_entity_id(
  226. "sensor", DOMAIN, f"{group_config_entry.entry_id}_group_All Locks_last_action"
  227. ) is None