"""Unit tests for RoomStateSwitch, ModeSyncSwitch, and OverrideReservationSwitch. Uses object.__new__ to bypass HA entity setup. async_write_ha_state is replaced with a MagicMock to avoid needing a real HA instance. """ from unittest.mock import AsyncMock, MagicMock, patch import pytest from custom_components.room_cal_ha.const import CONF_ZLOCK_ENTRIES from custom_components.room_cal_ha.switch import RoomStateSwitch, ModeSyncSwitch, OverrideReservationSwitch, UseUnifiSwitch, UseZlockSlotsSwitch def _room_state(): s = object.__new__(RoomStateSwitch) s._state = False s._deferred = True s._coordinator = MagicMock() s.async_write_ha_state = MagicMock() return s def _mode_sync(): s = object.__new__(ModeSyncSwitch) s._state = False s._coordinator = MagicMock() s._coordinator.cancel_mode_sync = MagicMock() s._coordinator.async_reconcile_from_store = AsyncMock() s.async_write_ha_state = MagicMock() return s # --------------------------------------------------------------------------- # RoomStateSwitch — basic state # --------------------------------------------------------------------------- async def test_room_state_initial_state_is_false(): s = _room_state() assert s.is_on is False async def test_room_state_turn_on_sets_state(): s = _room_state() s._deferred = False await s.async_turn_on() assert s.is_on is True async def test_room_state_turn_off_sets_state(): s = _room_state() s._state = True s._deferred = False await s.async_turn_off() assert s.is_on is False # --------------------------------------------------------------------------- # RoomStateSwitch — deferred write behavior # --------------------------------------------------------------------------- async def test_room_state_deferred_suppresses_write(): s = _room_state() assert s._deferred is True s.async_write_ha_state() # should be a no-op when deferred # The real async_write_ha_state is replaced; calling it when deferred returns early. # We verify by re-implementing the check: with patch.object(type(s).__bases__[0], "async_write_ha_state") as mock_parent: s.async_write_ha_state = RoomStateSwitch.async_write_ha_state.__get__(s, type(s)) s.async_write_ha_state() mock_parent.assert_not_called() def test_room_state_commit_state_unblocks_and_writes(): s = _room_state() s._state = True real_write = MagicMock() with patch( "homeassistant.helpers.entity.Entity.async_write_ha_state", real_write, ): s.commit_state() assert s._deferred is False real_write.assert_called_once() # --------------------------------------------------------------------------- # ModeSyncSwitch — turn off cancels mode sync # --------------------------------------------------------------------------- async def test_mode_sync_turn_off_cancels_sync(): s = _mode_sync() s._state = True await s.async_turn_off() assert s.is_on is False s._coordinator.cancel_mode_sync.assert_called_once() async def test_mode_sync_turn_on_does_not_cancel_sync(): s = _mode_sync() await s.async_turn_on() s._coordinator.cancel_mode_sync.assert_not_called() async def test_mode_sync_turn_on_triggers_reconcile(): s = _mode_sync() await s.async_turn_on() assert s.is_on is True s._coordinator.async_reconcile_from_store.assert_awaited_once() async def test_mode_sync_initial_state_is_false(): s = _mode_sync() assert s.is_on is False # --------------------------------------------------------------------------- # OverrideReservationSwitch # --------------------------------------------------------------------------- def _override_switch(override_active: bool = False): s = object.__new__(OverrideReservationSwitch) s.coordinator = MagicMock() s.coordinator.async_end_override = AsyncMock() s.coordinator.async_ensure_override = AsyncMock() data = MagicMock() data.override_active = override_active s.coordinator.data = data return s def test_override_switch_is_off_when_not_active(): assert _override_switch(override_active=False).is_on is False def test_override_switch_is_on_when_active(): assert _override_switch(override_active=True).is_on is True def test_override_switch_none_data_is_off(): s = _override_switch() s.coordinator.data = None assert s.is_on is False async def test_override_switch_turn_off_ends_override(): s = _override_switch(override_active=True) await s.async_turn_off() s.coordinator.async_end_override.assert_awaited_once() async def test_override_switch_turn_on_snapshots_active(): s = _override_switch() await s.async_turn_on() s.coordinator.async_ensure_override.assert_awaited_once() s.coordinator.async_end_override.assert_not_awaited() # --------------------------------------------------------------------------- # UseUnifiSwitch # --------------------------------------------------------------------------- def _use_unifi(): s = object.__new__(UseUnifiSwitch) s._coordinator = MagicMock() s.async_write_ha_state = MagicMock() return s def test_use_unifi_default_state_is_true(): s = _use_unifi() assert s.is_on is True async def test_use_unifi_turn_off_sets_false(): s = _use_unifi() await s.async_turn_off() assert s.is_on is False async def test_use_unifi_turn_on_restores_true(): s = _use_unifi() s._state = False await s.async_turn_on() assert s.is_on is True # --------------------------------------------------------------------------- # UseZlockSlotsSwitch # --------------------------------------------------------------------------- def _use_zlock(entries: list | None = None): s = object.__new__(UseZlockSlotsSwitch) s._coordinator = MagicMock() s.async_write_ha_state = MagicMock() entry = MagicMock() entry.options = {CONF_ZLOCK_ENTRIES: entries} if entries is not None else {} s._entry = entry return s def test_use_zlock_default_state_is_false(): s = _use_zlock() assert s.is_on is False def test_use_zlock_available_false_when_no_entries_configured(): assert _use_zlock(entries=[]).available is False def test_use_zlock_available_false_when_options_missing(): assert _use_zlock().available is False def test_use_zlock_available_true_when_entries_configured(): assert _use_zlock(entries=["abc123"]).available is True async def test_use_zlock_turn_on_sets_true(): s = _use_zlock(entries=["abc123"]) await s.async_turn_on() assert s.is_on is True async def test_use_zlock_turn_off_sets_false(): s = _use_zlock(entries=["abc123"]) s._state = True await s.async_turn_off() assert s.is_on is False