test_switches.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. """Unit tests for RoomStateSwitch, ModeSyncSwitch, and OverrideReservationSwitch.
  2. Uses object.__new__ to bypass HA entity setup. async_write_ha_state is
  3. replaced with a MagicMock to avoid needing a real HA instance.
  4. """
  5. from unittest.mock import AsyncMock, MagicMock, patch
  6. import pytest
  7. from custom_components.room_cal_ha.const import CONF_ZLOCK_ENTRIES
  8. from custom_components.room_cal_ha.switch import RoomStateSwitch, ModeSyncSwitch, OverrideReservationSwitch, UseUnifiSwitch, UseZlockSlotsSwitch
  9. def _room_state():
  10. s = object.__new__(RoomStateSwitch)
  11. s._state = False
  12. s._deferred = True
  13. s._coordinator = MagicMock()
  14. s.async_write_ha_state = MagicMock()
  15. return s
  16. def _mode_sync():
  17. s = object.__new__(ModeSyncSwitch)
  18. s._state = False
  19. s._coordinator = MagicMock()
  20. s._coordinator.cancel_mode_sync = MagicMock()
  21. s._coordinator.async_reconcile_from_store = AsyncMock()
  22. s.async_write_ha_state = MagicMock()
  23. return s
  24. # ---------------------------------------------------------------------------
  25. # RoomStateSwitch — basic state
  26. # ---------------------------------------------------------------------------
  27. async def test_room_state_initial_state_is_false():
  28. s = _room_state()
  29. assert s.is_on is False
  30. async def test_room_state_turn_on_sets_state():
  31. s = _room_state()
  32. s._deferred = False
  33. await s.async_turn_on()
  34. assert s.is_on is True
  35. async def test_room_state_turn_off_sets_state():
  36. s = _room_state()
  37. s._state = True
  38. s._deferred = False
  39. await s.async_turn_off()
  40. assert s.is_on is False
  41. # ---------------------------------------------------------------------------
  42. # RoomStateSwitch — deferred write behavior
  43. # ---------------------------------------------------------------------------
  44. async def test_room_state_deferred_suppresses_write():
  45. s = _room_state()
  46. assert s._deferred is True
  47. s.async_write_ha_state() # should be a no-op when deferred
  48. # The real async_write_ha_state is replaced; calling it when deferred returns early.
  49. # We verify by re-implementing the check:
  50. with patch.object(type(s).__bases__[0], "async_write_ha_state") as mock_parent:
  51. s.async_write_ha_state = RoomStateSwitch.async_write_ha_state.__get__(s, type(s))
  52. s.async_write_ha_state()
  53. mock_parent.assert_not_called()
  54. def test_room_state_commit_state_unblocks_and_writes():
  55. s = _room_state()
  56. s._state = True
  57. real_write = MagicMock()
  58. with patch(
  59. "homeassistant.helpers.entity.Entity.async_write_ha_state",
  60. real_write,
  61. ):
  62. s.commit_state()
  63. assert s._deferred is False
  64. real_write.assert_called_once()
  65. # ---------------------------------------------------------------------------
  66. # ModeSyncSwitch — turn off cancels mode sync
  67. # ---------------------------------------------------------------------------
  68. async def test_mode_sync_turn_off_cancels_sync():
  69. s = _mode_sync()
  70. s._state = True
  71. await s.async_turn_off()
  72. assert s.is_on is False
  73. s._coordinator.cancel_mode_sync.assert_called_once()
  74. async def test_mode_sync_turn_on_does_not_cancel_sync():
  75. s = _mode_sync()
  76. await s.async_turn_on()
  77. s._coordinator.cancel_mode_sync.assert_not_called()
  78. async def test_mode_sync_turn_on_triggers_reconcile():
  79. s = _mode_sync()
  80. await s.async_turn_on()
  81. assert s.is_on is True
  82. s._coordinator.async_reconcile_from_store.assert_awaited_once()
  83. async def test_mode_sync_initial_state_is_false():
  84. s = _mode_sync()
  85. assert s.is_on is False
  86. # ---------------------------------------------------------------------------
  87. # OverrideReservationSwitch
  88. # ---------------------------------------------------------------------------
  89. def _override_switch(override_active: bool = False):
  90. s = object.__new__(OverrideReservationSwitch)
  91. s.coordinator = MagicMock()
  92. s.coordinator.async_end_override = AsyncMock()
  93. s.coordinator.async_ensure_override = AsyncMock()
  94. data = MagicMock()
  95. data.override_active = override_active
  96. s.coordinator.data = data
  97. return s
  98. def test_override_switch_is_off_when_not_active():
  99. assert _override_switch(override_active=False).is_on is False
  100. def test_override_switch_is_on_when_active():
  101. assert _override_switch(override_active=True).is_on is True
  102. def test_override_switch_none_data_is_off():
  103. s = _override_switch()
  104. s.coordinator.data = None
  105. assert s.is_on is False
  106. async def test_override_switch_turn_off_ends_override():
  107. s = _override_switch(override_active=True)
  108. await s.async_turn_off()
  109. s.coordinator.async_end_override.assert_awaited_once()
  110. async def test_override_switch_turn_on_snapshots_active():
  111. s = _override_switch()
  112. await s.async_turn_on()
  113. s.coordinator.async_ensure_override.assert_awaited_once()
  114. s.coordinator.async_end_override.assert_not_awaited()
  115. # ---------------------------------------------------------------------------
  116. # UseUnifiSwitch
  117. # ---------------------------------------------------------------------------
  118. def _use_unifi():
  119. s = object.__new__(UseUnifiSwitch)
  120. s._coordinator = MagicMock()
  121. s.async_write_ha_state = MagicMock()
  122. return s
  123. def test_use_unifi_default_state_is_true():
  124. s = _use_unifi()
  125. assert s.is_on is True
  126. async def test_use_unifi_turn_off_sets_false():
  127. s = _use_unifi()
  128. await s.async_turn_off()
  129. assert s.is_on is False
  130. async def test_use_unifi_turn_on_restores_true():
  131. s = _use_unifi()
  132. s._state = False
  133. await s.async_turn_on()
  134. assert s.is_on is True
  135. # ---------------------------------------------------------------------------
  136. # UseZlockSlotsSwitch
  137. # ---------------------------------------------------------------------------
  138. def _use_zlock(entries: list | None = None):
  139. s = object.__new__(UseZlockSlotsSwitch)
  140. s._coordinator = MagicMock()
  141. s.async_write_ha_state = MagicMock()
  142. entry = MagicMock()
  143. entry.options = {CONF_ZLOCK_ENTRIES: entries} if entries is not None else {}
  144. s._entry = entry
  145. return s
  146. def test_use_zlock_default_state_is_false():
  147. s = _use_zlock()
  148. assert s.is_on is False
  149. def test_use_zlock_available_false_when_no_entries_configured():
  150. assert _use_zlock(entries=[]).available is False
  151. def test_use_zlock_available_false_when_options_missing():
  152. assert _use_zlock().available is False
  153. def test_use_zlock_available_true_when_entries_configured():
  154. assert _use_zlock(entries=["abc123"]).available is True
  155. async def test_use_zlock_turn_on_sets_true():
  156. s = _use_zlock(entries=["abc123"])
  157. await s.async_turn_on()
  158. assert s.is_on is True
  159. async def test_use_zlock_turn_off_sets_false():
  160. s = _use_zlock(entries=["abc123"])
  161. s._state = True
  162. await s.async_turn_off()
  163. assert s.is_on is False