Преглед изворни кода

Fix startup reconcile not turning off guest mode when store was already cleared

When the integration was disabled mid-checkout (after the store write but before
guest-mode-off ran), the store held None on next startup. The reconcile only set
guest_on=False when it found an elapsed record; a None store produced state=None
and left guest mode unchanged. With mode sync on, any non-active state should
drive guest mode off unconditionally.
ZiB пре 4 месеци
родитељ
комит
0e2c283d8f
2 измењених фајлова са 20 додато и 6 уклоњено
  1. 1 6
      custom_components/room_cal_ha/coordinator.py
  2. 19 0
      tests/test_coordinator.py

+ 1 - 6
custom_components/room_cal_ha/coordinator.py

@@ -280,17 +280,12 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
         current = nxt
 
       state = self._reservation_window_state(current)
-      guest_on: bool | None = None
       if state == "elapsed":
         await self.store.async_set(STORE_CURRENT, None)
         self._current_last_refresh = dt_util.utcnow()
         current = self._empty_reservation()
-        guest_on = False
-      elif state == "active":
-        guest_on = True
 
-      if guest_on is not None:
-        await self._async_set_room_state(guest_on, _startup=_startup)
+      await self._async_set_room_state(state == "active", _startup=_startup)
 
     real_current = self.store.get(STORE_CURRENT) or self._empty_reservation()
     real_next = self.store.get(STORE_NEXT) or self._empty_reservation()

+ 19 - 0
tests/test_coordinator.py

@@ -754,6 +754,25 @@ async def test_reconcile_promotes_next_when_check_in_elapsed(coordinator: RoomCa
   assert coordinator.store.get(STORE_CURRENT).guest_name == "Active Guest"
 
 
+async def test_reconcile_turns_off_guest_mode_when_store_already_cleared(coordinator: RoomCalCoordinator):
+  """Store was cleared (e.g. checkout write ran) but guest-mode-off action never ran."""
+  # No current reservation in store — it was already cleared before HA was disabled.
+  mock_switch = MagicMock()
+  mock_switch.async_turn_on = AsyncMock()
+  mock_switch.async_turn_off = AsyncMock()
+  coordinator._room_state_switch = mock_switch
+
+  with (
+    patch.object(coordinator, "_is_mode_sync_on", return_value=True),
+    patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_RECONCILE_NOW),
+  ):
+    await coordinator.async_reconcile_from_store()
+  coordinator.cancel_mode_sync()
+
+  mock_switch.async_turn_off.assert_awaited_once()
+  mock_switch.async_turn_on.assert_not_awaited()
+
+
 async def test_reconcile_does_not_touch_guest_mode_when_mode_sync_off(coordinator: RoomCalCoordinator):
   await coordinator.store.async_set(STORE_CURRENT, _ACTIVE)
   mock_switch = MagicMock()