|
@@ -1,8 +1,8 @@
|
|
|
-from datetime import date, time
|
|
|
|
|
|
|
+from datetime import date, datetime, time
|
|
|
|
|
|
|
|
import pytest
|
|
import pytest
|
|
|
|
|
|
|
|
-from custom_components.room_cal_ha.models import OverrideData, ReservationData
|
|
|
|
|
|
|
+from custom_components.room_cal_ha.models import GuestStateDiff, OverrideData, ReservationData, reservation_diff
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ---------------------------------------------------------------------------
|
|
@@ -171,3 +171,126 @@ def test_override_data_with_next():
|
|
|
o = OverrideData(current=_RES_A, next=_RES_B)
|
|
o = OverrideData(current=_RES_A, next=_RES_B)
|
|
|
assert o.current.guest_name == "Alice"
|
|
assert o.current.guest_name == "Alice"
|
|
|
assert o.next.guest_name == "Bob"
|
|
assert o.next.guest_name == "Bob"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
|
|
+# reservation_diff
|
|
|
|
|
+# ---------------------------------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+def _res(checkin_h: int, checkout_h: int, *, wifi_key="key", wifi_ssid="ssid") -> ReservationData:
|
|
|
|
|
+ """Build a reservation on 2026-07-01 with given check-in/out hours."""
|
|
|
|
|
+ return ReservationData(
|
|
|
|
|
+ guest_name="Guest",
|
|
|
|
|
+ check_in_date=date(2026, 7, 1),
|
|
|
|
|
+ check_in_time=time(checkin_h, 0),
|
|
|
|
|
+ check_out_date=date(2026, 7, 1),
|
|
|
|
|
+ check_out_time=time(checkout_h, 0),
|
|
|
|
|
+ wifi_ssid=wifi_ssid,
|
|
|
|
|
+ wifi_key=wifi_key,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+_DURING = datetime(2026, 7, 1, 15, 0) # 15:00 — inside a 14:00–12:00 window
|
|
|
|
|
+_BEFORE = datetime(2026, 7, 1, 12, 0) # 12:00 — before 14:00 check-in
|
|
|
|
|
+_AFTER = datetime(2026, 7, 1, 22, 0) # 22:00 — after typical check-out
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_both_inactive_returns_no_changes():
|
|
|
|
|
+ diff = reservation_diff(None, None, _DURING)
|
|
|
|
|
+ assert not diff.has_changes
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_old_none_new_active_returns_checkin():
|
|
|
|
|
+ res = _res(14, 20)
|
|
|
|
|
+ diff = reservation_diff(None, res, _DURING)
|
|
|
|
|
+ assert diff.checkin_needed
|
|
|
|
|
+ assert not diff.checkout_needed
|
|
|
|
|
+ assert diff.departing is None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_old_active_new_none_returns_checkout():
|
|
|
|
|
+ res = _res(14, 20)
|
|
|
|
|
+ diff = reservation_diff(res, None, _DURING)
|
|
|
|
|
+ assert diff.checkout_needed
|
|
|
|
|
+ assert not diff.checkin_needed
|
|
|
|
|
+ assert diff.departing is res
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_both_elapsed_returns_no_changes():
|
|
|
|
|
+ # Window already passed for both — time-based handler owns this transition, not diff.
|
|
|
|
|
+ old = _res(14, 20)
|
|
|
|
|
+ diff = reservation_diff(old, old, _AFTER)
|
|
|
|
|
+ assert not diff.has_changes
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_backend_clears_active_reservation_returns_checkout():
|
|
|
|
|
+ # Backend returns None while we're still inside the window → checkout needed.
|
|
|
|
|
+ old = _res(14, 20)
|
|
|
|
|
+ now = datetime(2026, 7, 1, 16, 0)
|
|
|
|
|
+ diff = reservation_diff(old, None, now)
|
|
|
|
|
+ assert diff.checkout_needed
|
|
|
|
|
+ assert not diff.checkin_needed
|
|
|
|
|
+ assert diff.departing is old
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_same_window_no_changes_returns_empty():
|
|
|
|
|
+ res = _res(14, 20)
|
|
|
|
|
+ diff = reservation_diff(res, res, _DURING)
|
|
|
|
|
+ assert not diff.has_changes
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_same_window_wifi_key_changed():
|
|
|
|
|
+ old = _res(14, 20, wifi_key="old-key")
|
|
|
|
|
+ new = _res(14, 20, wifi_key="new-key")
|
|
|
|
|
+ diff = reservation_diff(old, new, _DURING)
|
|
|
|
|
+ assert diff.wifi_key_changed
|
|
|
|
|
+ assert not diff.wifi_ssid_changed
|
|
|
|
|
+ assert not diff.checkin_needed
|
|
|
|
|
+ assert not diff.checkout_needed
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_same_window_wifi_ssid_changed():
|
|
|
|
|
+ old = _res(14, 20, wifi_ssid="OldNet")
|
|
|
|
|
+ new = _res(14, 20, wifi_ssid="NewNet")
|
|
|
|
|
+ diff = reservation_diff(old, new, _DURING)
|
|
|
|
|
+ assert diff.wifi_ssid_changed
|
|
|
|
|
+ assert not diff.wifi_key_changed
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_different_window_both_active_returns_checkout_and_checkin():
|
|
|
|
|
+ old = ReservationData(
|
|
|
|
|
+ check_in_date=date(2026, 7, 1), check_in_time=time(14, 0),
|
|
|
|
|
+ check_out_date=date(2026, 7, 2), check_out_time=time(12, 0),
|
|
|
|
|
+ wifi_ssid="s", wifi_key="k",
|
|
|
|
|
+ )
|
|
|
|
|
+ new = ReservationData(
|
|
|
|
|
+ check_in_date=date(2026, 7, 1), check_in_time=time(10, 0),
|
|
|
|
|
+ check_out_date=date(2026, 7, 2), check_out_time=time(12, 0),
|
|
|
|
|
+ wifi_ssid="s", wifi_key="k",
|
|
|
|
|
+ )
|
|
|
|
|
+ now = datetime(2026, 7, 1, 15, 0) # after both check-ins
|
|
|
|
|
+ diff = reservation_diff(old, new, now)
|
|
|
|
|
+ assert diff.checkout_needed
|
|
|
|
|
+ assert diff.checkin_needed
|
|
|
|
|
+ assert diff.departing is old
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_inactive_old_becomes_active_new_returns_checkin():
|
|
|
|
|
+ res = _res(14, 20)
|
|
|
|
|
+ diff = reservation_diff(res, res, _BEFORE) # same record, but we're before check-in
|
|
|
|
|
+ # old is inactive (before window), new is inactive too → no changes
|
|
|
|
|
+ assert not diff.has_changes
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_diff_no_checkin_time_treated_as_inactive():
|
|
|
|
|
+ res = ReservationData(check_in_date=date(2026, 7, 1)) # no time fields
|
|
|
|
|
+ diff = reservation_diff(None, res, _DURING)
|
|
|
|
|
+ assert not diff.has_changes
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_guest_state_diff_has_changes_false_when_empty():
|
|
|
|
|
+ assert not GuestStateDiff().has_changes
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def test_guest_state_diff_has_changes_true_for_checkin():
|
|
|
|
|
+ assert GuestStateDiff(checkin_needed=True).has_changes
|