test_switches.py 6.1 KB

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