test_switches.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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. data = MagicMock()
  94. data.override_active = override_active
  95. s.coordinator.data = data
  96. return s
  97. def test_override_switch_is_off_when_not_active():
  98. assert _override_switch(override_active=False).is_on is False
  99. def test_override_switch_is_on_when_active():
  100. assert _override_switch(override_active=True).is_on is True
  101. def test_override_switch_none_data_is_off():
  102. s = _override_switch()
  103. s.coordinator.data = None
  104. assert s.is_on is False
  105. async def test_override_switch_turn_off_ends_override():
  106. s = _override_switch(override_active=True)
  107. await s.async_turn_off()
  108. s.coordinator.async_end_override.assert_awaited_once()
  109. async def test_override_switch_turn_on_is_noop():
  110. s = _override_switch()
  111. await s.async_turn_on()
  112. s.coordinator.async_end_override.assert_not_awaited()
  113. # ---------------------------------------------------------------------------
  114. # UseUnifiSwitch
  115. # ---------------------------------------------------------------------------
  116. def _use_unifi():
  117. s = object.__new__(UseUnifiSwitch)
  118. s._coordinator = MagicMock()
  119. s.async_write_ha_state = MagicMock()
  120. return s
  121. def test_use_unifi_default_state_is_true():
  122. s = _use_unifi()
  123. assert s.is_on is True
  124. async def test_use_unifi_turn_off_sets_false():
  125. s = _use_unifi()
  126. await s.async_turn_off()
  127. assert s.is_on is False
  128. async def test_use_unifi_turn_on_restores_true():
  129. s = _use_unifi()
  130. s._state = False
  131. await s.async_turn_on()
  132. assert s.is_on is True
  133. # ---------------------------------------------------------------------------
  134. # UseZlockSlotsSwitch
  135. # ---------------------------------------------------------------------------
  136. def _use_zlock(entries: list | None = None):
  137. s = object.__new__(UseZlockSlotsSwitch)
  138. s._coordinator = MagicMock()
  139. s.async_write_ha_state = MagicMock()
  140. entry = MagicMock()
  141. entry.options = {CONF_ZLOCK_ENTRIES: entries} if entries is not None else {}
  142. s._entry = entry
  143. return s
  144. def test_use_zlock_default_state_is_false():
  145. s = _use_zlock()
  146. assert s.is_on is False
  147. def test_use_zlock_available_false_when_no_entries_configured():
  148. assert _use_zlock(entries=[]).available is False
  149. def test_use_zlock_available_false_when_options_missing():
  150. assert _use_zlock().available is False
  151. def test_use_zlock_available_true_when_entries_configured():
  152. assert _use_zlock(entries=["abc123"]).available is True
  153. async def test_use_zlock_turn_on_sets_true():
  154. s = _use_zlock(entries=["abc123"])
  155. await s.async_turn_on()
  156. assert s.is_on is True
  157. async def test_use_zlock_turn_off_sets_false():
  158. s = _use_zlock(entries=["abc123"])
  159. s._state = True
  160. await s.async_turn_off()
  161. assert s.is_on is False