|
@@ -187,21 +187,32 @@ async def test_thin_update_overlays_fields(coordinator: RoomCalCoordinator):
|
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
|
await coordinator.async_refresh()
|
|
await coordinator.async_refresh()
|
|
|
|
|
|
|
|
- await coordinator.async_apply_update(STORE_CURRENT, {"wifi_ssid": "GuestNet", "door_code": "9999"})
|
|
|
|
|
|
|
+ await coordinator.async_apply_update({"wifi_ssid": "GuestNet", "door_code": "9999"})
|
|
|
|
|
|
|
|
- assert coordinator.data.current.wifi_ssid == "GuestNet"
|
|
|
|
|
- assert coordinator.data.current.door_code == "9999"
|
|
|
|
|
- assert coordinator.data.current.guest_name == "Alice"
|
|
|
|
|
- assert coordinator.data.current.wifi_key == "stone-angel-market"
|
|
|
|
|
|
|
+ assert coordinator.data.active.wifi_ssid == "GuestNet"
|
|
|
|
|
+ assert coordinator.data.active.door_code == "9999"
|
|
|
|
|
+ assert coordinator.data.active.guest_name == "Alice"
|
|
|
|
|
+ assert coordinator.data.active.wifi_key == "stone-angel-market"
|
|
|
|
|
+ # Current (pure backend mirror) is unaffected by the patch.
|
|
|
|
|
+ assert coordinator.data.current.door_code == "0472"
|
|
|
|
|
|
|
|
|
|
|
|
|
-async def test_thin_update_updates_last_refresh(coordinator: RoomCalCoordinator):
|
|
|
|
|
|
|
+async def test_thin_update_starts_an_override(coordinator: RoomCalCoordinator):
|
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
|
await coordinator.async_refresh()
|
|
await coordinator.async_refresh()
|
|
|
- assert coordinator.data.current_last_refresh is None
|
|
|
|
|
|
|
+ assert coordinator.data.override_active is False
|
|
|
|
|
+
|
|
|
|
|
+ await coordinator.async_apply_update({"door_code": "1234"})
|
|
|
|
|
+ assert coordinator.data.override_active is True
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+async def test_thin_update_updates_active_last_refresh(coordinator: RoomCalCoordinator):
|
|
|
|
|
+ await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
|
|
|
+ await coordinator.async_refresh()
|
|
|
|
|
+ assert coordinator.data.active_last_refresh is None
|
|
|
|
|
|
|
|
- await coordinator.async_apply_update(STORE_CURRENT, {"door_code": "1234"})
|
|
|
|
|
- assert coordinator.data.current_last_refresh is not None
|
|
|
|
|
|
|
+ await coordinator.async_apply_update({"door_code": "1234"})
|
|
|
|
|
+ assert coordinator.data.active_last_refresh is not None
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ---------------------------------------------------------------------------
|
|
@@ -243,8 +254,8 @@ async def test_last_updated_set_on_thin_update_with_change(coordinator: RoomCalC
|
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
|
await coordinator.async_refresh()
|
|
await coordinator.async_refresh()
|
|
|
|
|
|
|
|
- await coordinator.async_apply_update(STORE_CURRENT, {"door_code": "9999"})
|
|
|
|
|
- assert coordinator.data.current_last_updated is not None
|
|
|
|
|
|
|
+ await coordinator.async_apply_update({"door_code": "9999"})
|
|
|
|
|
+ assert coordinator.data.active_last_updated is not None
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_last_updated_not_set_on_thin_update_with_same_value(coordinator: RoomCalCoordinator):
|
|
async def test_last_updated_not_set_on_thin_update_with_same_value(coordinator: RoomCalCoordinator):
|
|
@@ -252,33 +263,33 @@ async def test_last_updated_not_set_on_thin_update_with_same_value(coordinator:
|
|
|
await coordinator.async_refresh()
|
|
await coordinator.async_refresh()
|
|
|
|
|
|
|
|
# First update sets last_updated
|
|
# First update sets last_updated
|
|
|
- await coordinator.async_apply_update(STORE_CURRENT, {"door_code": "9999"})
|
|
|
|
|
- ts = coordinator.data.current_last_updated
|
|
|
|
|
|
|
+ await coordinator.async_apply_update({"door_code": "9999"})
|
|
|
|
|
+ ts = coordinator.data.active_last_updated
|
|
|
|
|
|
|
|
# Second update with the same value must not advance last_updated
|
|
# Second update with the same value must not advance last_updated
|
|
|
- await coordinator.async_apply_update(STORE_CURRENT, {"door_code": "9999"})
|
|
|
|
|
- assert coordinator.data.current_last_updated == ts
|
|
|
|
|
|
|
+ await coordinator.async_apply_update({"door_code": "9999"})
|
|
|
|
|
+ assert coordinator.data.active_last_updated == ts
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_last_updated_set_when_override_starts(coordinator: RoomCalCoordinator):
|
|
async def test_last_updated_set_when_override_starts(coordinator: RoomCalCoordinator):
|
|
|
await coordinator.async_refresh()
|
|
await coordinator.async_refresh()
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
- assert coordinator.data.current_last_updated is None
|
|
|
|
|
|
|
+ assert coordinator.data.active_last_updated is None
|
|
|
|
|
|
|
|
- await coordinator.async_start_override(OverrideData(current=ReservationData(guest_name="Override")))
|
|
|
|
|
- assert coordinator.data.current_last_updated is not None
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=ReservationData(guest_name="Override")))
|
|
|
|
|
+ assert coordinator.data.active_last_updated is not None
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_last_updated_set_when_override_ends(coordinator: RoomCalCoordinator):
|
|
async def test_last_updated_set_when_override_ends(coordinator: RoomCalCoordinator):
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
- await coordinator.async_start_override(OverrideData(current=ReservationData(guest_name="Override")))
|
|
|
|
|
- ts_start = coordinator.data.current_last_updated
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=ReservationData(guest_name="Override")))
|
|
|
|
|
+ ts_start = coordinator.data.active_last_updated
|
|
|
|
|
|
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
|
- coordinator._override = OverrideData(current=ReservationData(guest_name="Override"))
|
|
|
|
|
|
|
+ coordinator._override = OverrideData(active=ReservationData(guest_name="Override"))
|
|
|
await coordinator.async_end_override()
|
|
await coordinator.async_end_override()
|
|
|
|
|
|
|
|
- assert coordinator.data.current_last_updated != ts_start
|
|
|
|
|
|
|
+ assert coordinator.data.active_last_updated != ts_start
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_last_updated_persists_across_reload(hass: HomeAssistant, mock_entry):
|
|
async def test_last_updated_persists_across_reload(hass: HomeAssistant, mock_entry):
|
|
@@ -544,15 +555,17 @@ _OVERRIDE_RES = ReservationData(guest_name="Override Guest", door_code="1111")
|
|
|
_WIFI_OVERRIDE = ReservationData(guest_name="WiFi Guest", wifi_ssid="GuestNet", wifi_key="secret")
|
|
_WIFI_OVERRIDE = ReservationData(guest_name="WiFi Guest", wifi_ssid="GuestNet", wifi_key="secret")
|
|
|
|
|
|
|
|
|
|
|
|
|
-async def test_start_override_sets_current_in_data(coordinator: RoomCalCoordinator):
|
|
|
|
|
|
|
+async def test_start_override_sets_active_in_data(coordinator: RoomCalCoordinator):
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
- await coordinator.async_start_override(OverrideData(current=_OVERRIDE_RES))
|
|
|
|
|
- assert coordinator.data.current.guest_name == "Override Guest"
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=_OVERRIDE_RES))
|
|
|
|
|
+ assert coordinator.data.active.guest_name == "Override Guest"
|
|
|
|
|
+ # Current (pure backend mirror) is unaffected by the override.
|
|
|
|
|
+ assert coordinator.data.current.guest_name is None
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_start_override_sets_override_active_flag(coordinator: RoomCalCoordinator):
|
|
async def test_start_override_sets_override_active_flag(coordinator: RoomCalCoordinator):
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
- await coordinator.async_start_override(OverrideData(current=_OVERRIDE_RES))
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=_OVERRIDE_RES))
|
|
|
assert coordinator.data.override_active is True
|
|
assert coordinator.data.override_active is True
|
|
|
|
|
|
|
|
|
|
|
|
@@ -560,7 +573,7 @@ async def test_start_override_cancels_mode_sync(coordinator: RoomCalCoordinator)
|
|
|
cancel1, cancel2 = MagicMock(), MagicMock()
|
|
cancel1, cancel2 = MagicMock(), MagicMock()
|
|
|
coordinator._mode_sync_handles = [cancel1, cancel2]
|
|
coordinator._mode_sync_handles = [cancel1, cancel2]
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
- await coordinator.async_start_override(OverrideData(current=_OVERRIDE_RES))
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=_OVERRIDE_RES))
|
|
|
cancel1.assert_called_once()
|
|
cancel1.assert_called_once()
|
|
|
cancel2.assert_called_once()
|
|
cancel2.assert_called_once()
|
|
|
assert coordinator._mode_sync_handles == []
|
|
assert coordinator._mode_sync_handles == []
|
|
@@ -579,7 +592,7 @@ async def test_start_override_turns_on_guest_mode(coordinator: RoomCalCoordinato
|
|
|
patch.object(coordinator, "_is_mode_sync_on", return_value=True),
|
|
patch.object(coordinator, "_is_mode_sync_on", return_value=True),
|
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
|
):
|
|
):
|
|
|
- await coordinator.async_start_override(OverrideData(current=active_override))
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=active_override))
|
|
|
coordinator.cancel_mode_sync()
|
|
coordinator.cancel_mode_sync()
|
|
|
mock_switch.async_turn_on.assert_awaited_once()
|
|
mock_switch.async_turn_on.assert_awaited_once()
|
|
|
|
|
|
|
@@ -589,7 +602,7 @@ async def test_start_override_does_not_turn_on_guest_mode_when_mode_sync_off(coo
|
|
|
mock_switch.async_turn_on = AsyncMock()
|
|
mock_switch.async_turn_on = AsyncMock()
|
|
|
coordinator._room_state_switch = mock_switch
|
|
coordinator._room_state_switch = mock_switch
|
|
|
with patch.object(coordinator, "_is_mode_sync_on", return_value=False):
|
|
with patch.object(coordinator, "_is_mode_sync_on", return_value=False):
|
|
|
- await coordinator.async_start_override(OverrideData(current=_OVERRIDE_RES))
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=_OVERRIDE_RES))
|
|
|
mock_switch.async_turn_on.assert_not_awaited()
|
|
mock_switch.async_turn_on.assert_not_awaited()
|
|
|
|
|
|
|
|
|
|
|
|
@@ -606,7 +619,7 @@ async def test_start_override_calls_wifi_when_credentials_present(hass: HomeAssi
|
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
|
patch("custom_components.room_cal_ha.wifi.async_set_wifi_key", new_callable=AsyncMock) as mock_wifi,
|
|
patch("custom_components.room_cal_ha.wifi.async_set_wifi_key", new_callable=AsyncMock) as mock_wifi,
|
|
|
):
|
|
):
|
|
|
- await coordinator.async_start_override(OverrideData(current=active_wifi_override))
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=active_wifi_override))
|
|
|
coordinator.cancel_mode_sync()
|
|
coordinator.cancel_mode_sync()
|
|
|
mock_wifi.assert_awaited_once_with(hass, "GuestNet", "secret")
|
|
mock_wifi.assert_awaited_once_with(hass, "GuestNet", "secret")
|
|
|
|
|
|
|
@@ -617,7 +630,7 @@ async def test_start_override_skips_wifi_when_mode_sync_off(coordinator: RoomCal
|
|
|
patch.object(coordinator, "_is_mode_sync_on", return_value=False),
|
|
patch.object(coordinator, "_is_mode_sync_on", return_value=False),
|
|
|
patch("custom_components.room_cal_ha.wifi.async_set_wifi_key", new_callable=AsyncMock) as mock_wifi,
|
|
patch("custom_components.room_cal_ha.wifi.async_set_wifi_key", new_callable=AsyncMock) as mock_wifi,
|
|
|
):
|
|
):
|
|
|
- await coordinator.async_start_override(OverrideData(current=_WIFI_OVERRIDE))
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=_WIFI_OVERRIDE))
|
|
|
mock_wifi.assert_not_awaited()
|
|
mock_wifi.assert_not_awaited()
|
|
|
|
|
|
|
|
|
|
|
|
@@ -625,24 +638,19 @@ async def test_start_override_skips_wifi_when_ssid_missing(coordinator: RoomCalC
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
no_ssid = ReservationData(wifi_ssid=None, wifi_key="secret")
|
|
no_ssid = ReservationData(wifi_ssid=None, wifi_key="secret")
|
|
|
with patch("custom_components.room_cal_ha.wifi.async_set_wifi_key", new_callable=AsyncMock) as mock_wifi:
|
|
with patch("custom_components.room_cal_ha.wifi.async_set_wifi_key", new_callable=AsyncMock) as mock_wifi:
|
|
|
- await coordinator.async_start_override(OverrideData(current=no_ssid))
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=no_ssid))
|
|
|
mock_wifi.assert_not_awaited()
|
|
mock_wifi.assert_not_awaited()
|
|
|
|
|
|
|
|
|
|
|
|
|
-async def test_start_override_uses_store_next_when_override_next_is_none(coordinator: RoomCalCoordinator):
|
|
|
|
|
|
|
+async def test_start_override_next_always_mirrors_store(coordinator: RoomCalCoordinator):
|
|
|
|
|
+ """There's no such thing as an override-owned Next — Next always mirrors the
|
|
|
|
|
+ store's own feed, regardless of any override on active."""
|
|
|
await coordinator.store.async_set(STORE_NEXT, SAMPLE)
|
|
await coordinator.store.async_set(STORE_NEXT, SAMPLE)
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
- await coordinator.async_start_override(OverrideData(current=_OVERRIDE_RES))
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=_OVERRIDE_RES))
|
|
|
assert coordinator.data.next.guest_name == "Alice"
|
|
assert coordinator.data.next.guest_name == "Alice"
|
|
|
|
|
|
|
|
|
|
|
|
|
-async def test_start_override_uses_override_next_when_provided(coordinator: RoomCalCoordinator):
|
|
|
|
|
- override_next = ReservationData(guest_name="Override Next")
|
|
|
|
|
- coordinator._room_state_switch = None
|
|
|
|
|
- await coordinator.async_start_override(OverrideData(current=_OVERRIDE_RES, next=override_next))
|
|
|
|
|
- assert coordinator.data.next.guest_name == "Override Next"
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
async def test_start_override_applies_defaults_to_current(hass: HomeAssistant):
|
|
async def test_start_override_applies_defaults_to_current(hass: HomeAssistant):
|
|
|
entry = MockConfigEntry(
|
|
entry = MockConfigEntry(
|
|
|
domain=DOMAIN,
|
|
domain=DOMAIN,
|
|
@@ -658,11 +666,11 @@ async def test_start_override_applies_defaults_to_current(hass: HomeAssistant):
|
|
|
coord._guest_mode_switch = None
|
|
coord._guest_mode_switch = None
|
|
|
|
|
|
|
|
partial_override = ReservationData(guest_name="Override Guest")
|
|
partial_override = ReservationData(guest_name="Override Guest")
|
|
|
- await coord.async_start_override(OverrideData(current=partial_override))
|
|
|
|
|
|
|
+ await coord.async_start_override(OverrideData(active=partial_override))
|
|
|
|
|
|
|
|
- assert coord.data.current.guest_name == "Override Guest"
|
|
|
|
|
- assert coord.data.current.wifi_ssid == "DefaultSSID"
|
|
|
|
|
- assert coord.data.current.check_out_time == time(11, 0)
|
|
|
|
|
|
|
+ assert coord.data.active.guest_name == "Override Guest"
|
|
|
|
|
+ assert coord.data.active.wifi_ssid == "DefaultSSID"
|
|
|
|
|
+ assert coord.data.active.check_out_time == time(11, 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_start_override_defaults_do_not_overwrite_explicit_fields(hass: HomeAssistant):
|
|
async def test_start_override_defaults_do_not_overwrite_explicit_fields(hass: HomeAssistant):
|
|
@@ -680,9 +688,9 @@ async def test_start_override_defaults_do_not_overwrite_explicit_fields(hass: Ho
|
|
|
coord._guest_mode_switch = None
|
|
coord._guest_mode_switch = None
|
|
|
|
|
|
|
|
explicit_override = ReservationData(guest_name="Override Guest", wifi_ssid="ExplicitSSID")
|
|
explicit_override = ReservationData(guest_name="Override Guest", wifi_ssid="ExplicitSSID")
|
|
|
- await coord.async_start_override(OverrideData(current=explicit_override))
|
|
|
|
|
|
|
+ await coord.async_start_override(OverrideData(active=explicit_override))
|
|
|
|
|
|
|
|
- assert coord.data.current.wifi_ssid == "ExplicitSSID"
|
|
|
|
|
|
|
+ assert coord.data.active.wifi_ssid == "ExplicitSSID"
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_refresh_during_override_applies_defaults_to_current(hass: HomeAssistant):
|
|
async def test_refresh_during_override_applies_defaults_to_current(hass: HomeAssistant):
|
|
@@ -700,11 +708,11 @@ async def test_refresh_during_override_applies_defaults_to_current(hass: HomeAss
|
|
|
coord._guest_mode_switch = None
|
|
coord._guest_mode_switch = None
|
|
|
|
|
|
|
|
partial_override = ReservationData(guest_name="Override Guest")
|
|
partial_override = ReservationData(guest_name="Override Guest")
|
|
|
- await coord.async_start_override(OverrideData(current=partial_override))
|
|
|
|
|
|
|
+ await coord.async_start_override(OverrideData(active=partial_override))
|
|
|
await coord.async_refresh()
|
|
await coord.async_refresh()
|
|
|
|
|
|
|
|
- assert coord.data.current.guest_name == "Override Guest"
|
|
|
|
|
- assert coord.data.current.door_code == "9999"
|
|
|
|
|
|
|
+ assert coord.data.active.guest_name == "Override Guest"
|
|
|
|
|
+ assert coord.data.active.door_code == "9999"
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_start_override_applies_defaults_to_store_next(hass: HomeAssistant):
|
|
async def test_start_override_applies_defaults_to_store_next(hass: HomeAssistant):
|
|
@@ -722,7 +730,7 @@ async def test_start_override_applies_defaults_to_store_next(hass: HomeAssistant
|
|
|
coord._guest_mode_switch = None
|
|
coord._guest_mode_switch = None
|
|
|
|
|
|
|
|
await coord.store.async_set(STORE_NEXT, ReservationData(guest_name="Next Guest"))
|
|
await coord.store.async_set(STORE_NEXT, ReservationData(guest_name="Next Guest"))
|
|
|
- await coord.async_start_override(OverrideData(current=_OVERRIDE_RES))
|
|
|
|
|
|
|
+ await coord.async_start_override(OverrideData(active=_OVERRIDE_RES))
|
|
|
|
|
|
|
|
assert coord.data.next.guest_name == "Next Guest"
|
|
assert coord.data.next.guest_name == "Next Guest"
|
|
|
assert coord.data.next.wifi_ssid == "DefaultSSID"
|
|
assert coord.data.next.wifi_ssid == "DefaultSSID"
|
|
@@ -730,7 +738,7 @@ async def test_start_override_applies_defaults_to_store_next(hass: HomeAssistant
|
|
|
|
|
|
|
|
async def test_end_override_clears_override_active(coordinator: RoomCalCoordinator):
|
|
async def test_end_override_clears_override_active(coordinator: RoomCalCoordinator):
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
- coordinator._override = OverrideData(current=_OVERRIDE_RES)
|
|
|
|
|
|
|
+ coordinator._override = OverrideData(active=_OVERRIDE_RES)
|
|
|
await coordinator.async_end_override()
|
|
await coordinator.async_end_override()
|
|
|
assert coordinator.data.override_active is False
|
|
assert coordinator.data.override_active is False
|
|
|
|
|
|
|
@@ -738,14 +746,14 @@ async def test_end_override_clears_override_active(coordinator: RoomCalCoordinat
|
|
|
async def test_end_override_restores_store_current(coordinator: RoomCalCoordinator):
|
|
async def test_end_override_restores_store_current(coordinator: RoomCalCoordinator):
|
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
- coordinator._override = OverrideData(current=_OVERRIDE_RES)
|
|
|
|
|
|
|
+ coordinator._override = OverrideData(active=_OVERRIDE_RES)
|
|
|
await coordinator.async_end_override()
|
|
await coordinator.async_end_override()
|
|
|
- assert coordinator.data.current.guest_name == "Alice"
|
|
|
|
|
|
|
+ assert coordinator.data.active.guest_name == "Alice"
|
|
|
|
|
|
|
|
|
|
|
|
|
async def test_start_override_with_end_at_schedules_and_ends_override(coordinator: RoomCalCoordinator):
|
|
async def test_start_override_with_end_at_schedules_and_ends_override(coordinator: RoomCalCoordinator):
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
- override = OverrideData(current=ReservationData(guest_name="Timed Guest"))
|
|
|
|
|
|
|
+ override = OverrideData(active=ReservationData(guest_name="Timed Guest"))
|
|
|
with (
|
|
with (
|
|
|
patch.object(coordinator, "_is_mode_sync_on", return_value=False),
|
|
patch.object(coordinator, "_is_mode_sync_on", return_value=False),
|
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
@@ -768,7 +776,7 @@ async def test_stale_override_timeout_does_not_end_a_replacing_override(coordina
|
|
|
"""A timer from an override that was already manually ended/replaced must not end
|
|
"""A timer from an override that was already manually ended/replaced must not end
|
|
|
whatever override is active by the time it fires."""
|
|
whatever override is active by the time it fires."""
|
|
|
coordinator._room_state_switch = None
|
|
coordinator._room_state_switch = None
|
|
|
- override = OverrideData(current=ReservationData(guest_name="Timed Guest"))
|
|
|
|
|
|
|
+ override = OverrideData(active=ReservationData(guest_name="Timed Guest"))
|
|
|
with (
|
|
with (
|
|
|
patch.object(coordinator, "_is_mode_sync_on", return_value=False),
|
|
patch.object(coordinator, "_is_mode_sync_on", return_value=False),
|
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
@@ -779,7 +787,7 @@ async def test_stale_override_timeout_does_not_end_a_replacing_override(coordina
|
|
|
await coordinator.async_end_override()
|
|
await coordinator.async_end_override()
|
|
|
assert coordinator._override is None
|
|
assert coordinator._override is None
|
|
|
|
|
|
|
|
- new_override = OverrideData(current=ReservationData(guest_name="New Guest"))
|
|
|
|
|
|
|
+ new_override = OverrideData(active=ReservationData(guest_name="New Guest"))
|
|
|
with patch.object(coordinator, "_is_mode_sync_on", return_value=False):
|
|
with patch.object(coordinator, "_is_mode_sync_on", return_value=False):
|
|
|
await coordinator.async_start_override(new_override) # no end_at — no new timer
|
|
await coordinator.async_start_override(new_override) # no end_at — no new timer
|
|
|
# Simulate the original (stale) timer's callback firing late, after the replacement started.
|
|
# Simulate the original (stale) timer's callback firing late, after the replacement started.
|
|
@@ -813,7 +821,7 @@ async def test_start_override_empty_current_triggers_checkout_not_checkin(
|
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
|
patch("custom_components.room_cal_ha.wifi.async_set_ssid_enabled", new_callable=AsyncMock) as mock_ssid,
|
|
patch("custom_components.room_cal_ha.wifi.async_set_ssid_enabled", new_callable=AsyncMock) as mock_ssid,
|
|
|
):
|
|
):
|
|
|
- await coordinator.async_start_override(OverrideData(current=ReservationData()))
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=ReservationData()))
|
|
|
coordinator.cancel_mode_sync()
|
|
coordinator.cancel_mode_sync()
|
|
|
|
|
|
|
|
mock_ssid.assert_awaited_once_with(hass, "GuestNet", False)
|
|
mock_ssid.assert_awaited_once_with(hass, "GuestNet", False)
|
|
@@ -822,13 +830,13 @@ async def test_start_override_empty_current_triggers_checkout_not_checkin(
|
|
|
assert coordinator.data.override_active is True
|
|
assert coordinator.data.override_active is True
|
|
|
|
|
|
|
|
|
|
|
|
|
-async def test_override_current_checks_out_on_schedule_while_override_stays_active(
|
|
|
|
|
|
|
+async def test_override_active_checks_out_on_schedule_while_override_stays_active(
|
|
|
hass: HomeAssistant, coordinator: RoomCalCoordinator
|
|
hass: HomeAssistant, coordinator: RoomCalCoordinator
|
|
|
):
|
|
):
|
|
|
- """With Mode Sync on, an override's own Current schedules a real check-out handle;
|
|
|
|
|
- firing it clears the override's Current (not the store's), runs checkout actions,
|
|
|
|
|
- override_active stays True, and a subsequent Next check-in keeps writing into the
|
|
|
|
|
- override rather than the store."""
|
|
|
|
|
|
|
+ """With Mode Sync on, an override's own active record schedules a real check-out
|
|
|
|
|
+ handle; firing it clears the override's active (not the store's), runs checkout
|
|
|
|
|
+ actions, override_active stays True, and a subsequent Next check-in keeps writing
|
|
|
|
|
+ into the override rather than the store."""
|
|
|
override_guest = ReservationData(
|
|
override_guest = ReservationData(
|
|
|
guest_name="Override Guest",
|
|
guest_name="Override Guest",
|
|
|
check_in_date=date(2026, 7, 1), check_in_time=time(14, 0),
|
|
check_in_date=date(2026, 7, 1), check_in_time=time(14, 0),
|
|
@@ -843,7 +851,7 @@ async def test_override_current_checks_out_on_schedule_while_override_stays_acti
|
|
|
patch.object(coordinator, "_is_mode_sync_on", return_value=True),
|
|
patch.object(coordinator, "_is_mode_sync_on", return_value=True),
|
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
patch("custom_components.room_cal_ha.coordinator.dt_util.now", return_value=_AT_CHECK_IN),
|
|
|
):
|
|
):
|
|
|
- await coordinator.async_start_override(OverrideData(current=override_guest))
|
|
|
|
|
|
|
+ await coordinator.async_start_override(OverrideData(active=override_guest))
|
|
|
|
|
|
|
|
assert len(coordinator._mode_sync_handles) >= 1
|
|
assert len(coordinator._mode_sync_handles) >= 1
|
|
|
assert coordinator.data.override_active is True
|
|
assert coordinator.data.override_active is True
|
|
@@ -857,7 +865,7 @@ async def test_override_current_checks_out_on_schedule_while_override_stays_acti
|
|
|
|
|
|
|
|
mock_switch.async_turn_off.assert_awaited_once()
|
|
mock_switch.async_turn_off.assert_awaited_once()
|
|
|
assert coordinator.data.override_active is True
|
|
assert coordinator.data.override_active is True
|
|
|
- assert coordinator.data.current.guest_name is None
|
|
|
|
|
|
|
+ assert coordinator.data.active.guest_name is None
|
|
|
assert coordinator.store.get(STORE_CURRENT) is None
|
|
assert coordinator.store.get(STORE_CURRENT) is None
|
|
|
|
|
|
|
|
await coordinator.store.async_set(STORE_NEXT, ReservationData(guest_name="Next Guest"))
|
|
await coordinator.store.async_set(STORE_NEXT, ReservationData(guest_name="Next Guest"))
|
|
@@ -869,7 +877,7 @@ async def test_override_current_checks_out_on_schedule_while_override_stays_acti
|
|
|
coordinator.cancel_mode_sync()
|
|
coordinator.cancel_mode_sync()
|
|
|
|
|
|
|
|
assert coordinator.data.override_active is True
|
|
assert coordinator.data.override_active is True
|
|
|
- assert coordinator.data.current.guest_name == "Next Guest"
|
|
|
|
|
|
|
+ assert coordinator.data.active.guest_name == "Next Guest"
|
|
|
# The real store's own Current is untouched — the promoted value went into the override.
|
|
# The real store's own Current is untouched — the promoted value went into the override.
|
|
|
assert coordinator.store.get(STORE_CURRENT) is None
|
|
assert coordinator.store.get(STORE_CURRENT) is None
|
|
|
|
|
|
|
@@ -878,34 +886,38 @@ async def test_override_current_checks_out_on_schedule_while_override_stays_acti
|
|
|
# Thin updates during override
|
|
# Thin updates during override
|
|
|
# ---------------------------------------------------------------------------
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-async def test_thin_update_current_during_override_updates_override_not_store(coordinator: RoomCalCoordinator):
|
|
|
|
|
|
|
+async def test_thin_update_during_override_updates_override_not_store(coordinator: RoomCalCoordinator):
|
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
|
await coordinator.async_refresh()
|
|
await coordinator.async_refresh()
|
|
|
- coordinator._override = OverrideData(current=_OVERRIDE_RES)
|
|
|
|
|
|
|
+ coordinator._override = OverrideData(active=_OVERRIDE_RES)
|
|
|
|
|
|
|
|
- await coordinator.async_apply_update(STORE_CURRENT, {"door_code": "9999"})
|
|
|
|
|
|
|
+ await coordinator.async_apply_update({"door_code": "9999"})
|
|
|
|
|
|
|
|
- assert coordinator.data.current.door_code == "9999"
|
|
|
|
|
|
|
+ assert coordinator.data.active.door_code == "9999"
|
|
|
# Store is unchanged
|
|
# Store is unchanged
|
|
|
assert coordinator.store.get(STORE_CURRENT).door_code == "0472"
|
|
assert coordinator.store.get(STORE_CURRENT).door_code == "0472"
|
|
|
|
|
|
|
|
|
|
|
|
|
-async def test_thin_update_current_during_override_data_still_shows_override_active(coordinator: RoomCalCoordinator):
|
|
|
|
|
|
|
+async def test_thin_update_during_override_data_still_shows_override_active(coordinator: RoomCalCoordinator):
|
|
|
await coordinator.async_refresh()
|
|
await coordinator.async_refresh()
|
|
|
- coordinator._override = OverrideData(current=_OVERRIDE_RES)
|
|
|
|
|
- await coordinator.async_apply_update(STORE_CURRENT, {"door_code": "9999"})
|
|
|
|
|
|
|
+ coordinator._override = OverrideData(active=_OVERRIDE_RES)
|
|
|
|
|
+ await coordinator.async_apply_update({"door_code": "9999"})
|
|
|
assert coordinator.data.override_active is True
|
|
assert coordinator.data.override_active is True
|
|
|
|
|
|
|
|
|
|
|
|
|
-async def test_thin_update_next_during_override_falls_through_to_store(coordinator: RoomCalCoordinator):
|
|
|
|
|
- coordinator._override = OverrideData(current=_OVERRIDE_RES) # override.next is None
|
|
|
|
|
|
|
+async def test_thin_update_with_no_override_running_starts_one(coordinator: RoomCalCoordinator):
|
|
|
|
|
+ """Patching with no override active must snapshot `active` as an override first,
|
|
|
|
|
+ so the patch survives the next backend poll instead of being silently overwritten."""
|
|
|
|
|
+ await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
|
|
|
await coordinator.async_refresh()
|
|
await coordinator.async_refresh()
|
|
|
|
|
+ assert coordinator.data.override_active is False
|
|
|
|
|
|
|
|
- await coordinator.async_apply_update(STORE_NEXT, {"guest_name": "Next Guest"})
|
|
|
|
|
|
|
+ await coordinator.async_apply_update({"guest_name": "Patched Guest"})
|
|
|
|
|
|
|
|
- assert coordinator.store.get(STORE_NEXT).guest_name == "Next Guest"
|
|
|
|
|
assert coordinator.data.override_active is True
|
|
assert coordinator.data.override_active is True
|
|
|
- assert coordinator.data.current.guest_name == "Override Guest"
|
|
|
|
|
|
|
+ assert coordinator.data.active.guest_name == "Patched Guest"
|
|
|
|
|
+ # Store's own current is untouched by the patch.
|
|
|
|
|
+ assert coordinator.store.get(STORE_CURRENT).guest_name == "Alice"
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ---------------------------------------------------------------------------
|
|
@@ -916,7 +928,7 @@ async def test_backend_fetch_during_override_updates_store_but_preserves_overrid
|
|
|
entry = _make_entry(hass, {CONF_SYNC_MODE: SYNC_MODE_CALDAV, CONF_CALDAV_URL: "https://cal.example.com", CONF_CALENDAR_NAME: "Room"})
|
|
entry = _make_entry(hass, {CONF_SYNC_MODE: SYNC_MODE_CALDAV, CONF_CALDAV_URL: "https://cal.example.com", CONF_CALENDAR_NAME: "Room"})
|
|
|
coord = RoomCalCoordinator(hass, entry)
|
|
coord = RoomCalCoordinator(hass, entry)
|
|
|
await coord.async_setup()
|
|
await coord.async_setup()
|
|
|
- coord._override = OverrideData(current=_OVERRIDE_RES)
|
|
|
|
|
|
|
+ coord._override = OverrideData(active=_OVERRIDE_RES)
|
|
|
|
|
|
|
|
backend_data = ReservationData(guest_name="Backend Alice")
|
|
backend_data = ReservationData(guest_name="Backend Alice")
|
|
|
with patch("custom_components.room_cal_ha.coordinator.CalDavBackend") as MockBackend:
|
|
with patch("custom_components.room_cal_ha.coordinator.CalDavBackend") as MockBackend:
|
|
@@ -925,8 +937,9 @@ async def test_backend_fetch_during_override_updates_store_but_preserves_overrid
|
|
|
|
|
|
|
|
# Store was written by the backend
|
|
# Store was written by the backend
|
|
|
assert coord.store.get(STORE_CURRENT).guest_name == "Backend Alice"
|
|
assert coord.store.get(STORE_CURRENT).guest_name == "Backend Alice"
|
|
|
- # But displayed data still shows the override
|
|
|
|
|
- assert coord.data.current.guest_name == "Override Guest"
|
|
|
|
|
|
|
+ # But displayed active still shows the override; current mirrors the fresh backend write.
|
|
|
|
|
+ assert coord.data.active.guest_name == "Override Guest"
|
|
|
|
|
+ assert coord.data.current.guest_name == "Backend Alice"
|
|
|
assert coord.data.override_active is True
|
|
assert coord.data.override_active is True
|
|
|
|
|
|
|
|
|
|
|