"""Tests for time-of-day automation scheduling (weekday + timezone + overnight).""" import datetime as dt from ledgrab.core.automations import automation_engine as ae from ledgrab.core.automations.automation_engine import AutomationEngine, _now_in_tz from ledgrab.storage.automation import TimeOfDayRule _eval = AutomationEngine._evaluate_time_of_day def _patch_now(monkeypatch, fixed: dt.datetime) -> None: monkeypatch.setattr(ae, "_now_in_tz", lambda tz: fixed) def test_within_window_every_day(monkeypatch): _patch_now(monkeypatch, dt.datetime(2026, 6, 3, 20, 0)) assert _eval(TimeOfDayRule(start_time="18:00", end_time="23:00")) is True def test_outside_window(monkeypatch): _patch_now(monkeypatch, dt.datetime(2026, 6, 3, 12, 0)) assert _eval(TimeOfDayRule(start_time="18:00", end_time="23:00")) is False def test_weekday_filter(monkeypatch): fixed = dt.datetime(2026, 6, 3, 20, 0) wd = fixed.weekday() _patch_now(monkeypatch, fixed) assert _eval(TimeOfDayRule("time_of_day", "18:00", "23:00", days_of_week=[wd])) is True assert ( _eval(TimeOfDayRule("time_of_day", "18:00", "23:00", days_of_week=[(wd + 1) % 7])) is False ) def test_overnight_evening_uses_today(monkeypatch): fixed = dt.datetime(2026, 6, 3, 23, 0) # evening tail of a 22:00->06:00 window wd = fixed.weekday() _patch_now(monkeypatch, fixed) assert _eval(TimeOfDayRule("time_of_day", "22:00", "06:00", days_of_week=[wd])) is True assert ( _eval(TimeOfDayRule("time_of_day", "22:00", "06:00", days_of_week=[(wd + 1) % 7])) is False ) def test_overnight_morning_uses_yesterday(monkeypatch): fixed = dt.datetime(2026, 6, 3, 3, 0) # morning tail belongs to yesterday's window today = fixed.weekday() yesterday = (today - 1) % 7 _patch_now(monkeypatch, fixed) assert _eval(TimeOfDayRule("time_of_day", "22:00", "06:00", days_of_week=[yesterday])) is True assert _eval(TimeOfDayRule("time_of_day", "22:00", "06:00", days_of_week=[today])) is False def test_from_dict_filters_invalid_days(): rule = TimeOfDayRule.from_dict({"days_of_week": [0, 7, -1, 3, 3, "x", 2.0]}) assert rule.days_of_week == [0, 2, 3] def test_to_dict_round_trips_new_fields(): rule = TimeOfDayRule("time_of_day", "08:00", "20:00", days_of_week=[1, 2], timezone="UTC") d = rule.to_dict() assert d["days_of_week"] == [1, 2] assert d["timezone"] == "UTC" again = TimeOfDayRule.from_dict(d) assert again.days_of_week == [1, 2] and again.timezone == "UTC" def test_now_in_tz_invalid_falls_back_to_local(): assert _now_in_tz("Not/AZone").tzinfo is None def test_now_in_tz_valid_is_aware(): assert _now_in_tz("UTC").tzinfo is not None def test_now_in_tz_empty_is_local(): assert _now_in_tz("").tzinfo is None