"""Unit tests for schedule due-date logic (no DB).""" from __future__ import annotations from datetime import date, datetime, timezone from services.schedule import ( format_schedule_label, initial_due_at, is_due_on_date, next_due_after_completion, weekday_bit, weekday_mask_for_date, ) def test_interval_daily_label(): label = format_schedule_label({"schedule_kind": "interval", "interval_days": 1, "weekday_mask": 0}) assert label == "Täglich" def test_interval_seven_is_not_weekly(): label = format_schedule_label({"schedule_kind": "interval", "interval_days": 7, "weekday_mask": 0}) assert label == "Alle 7 Tage" def test_weekday_label_lists_days(): label = format_schedule_label( {"schedule_kind": "weekdays", "weekday_mask": weekday_bit(0) | weekday_bit(2) | weekday_bit(6)} ) assert label == "Mo, Mi, So" assert "Wöchentlich" not in label def test_weekday_mask_next_due_after_friday(): schedule = { "schedule_kind": "weekdays", "weekday_mask": weekday_bit(0) | weekday_bit(2) | weekday_bit(4), "pause_until": None, } friday = datetime(2026, 8, 7, 12, 0, tzinfo=timezone.utc) nxt = next_due_after_completion(schedule=schedule, completed_at=friday) assert nxt.date() == date(2026, 8, 10) def test_is_due_on_date_hides_future_weekday(): schedule = { "schedule_kind": "weekdays", "weekday_mask": weekday_bit(1), "pause_until": None, } next_due = datetime(2026, 8, 18, tzinfo=timezone.utc) assert is_due_on_date(schedule=schedule, next_due_at=next_due, on_date=date(2026, 8, 17)) is False assert is_due_on_date(schedule=schedule, next_due_at=next_due, on_date=date(2026, 8, 18)) is True def test_is_due_on_date_keeps_overdue_weekday(): schedule = { "schedule_kind": "weekdays", "weekday_mask": weekday_bit(0), "pause_until": None, } missed_monday = datetime(2026, 8, 17, tzinfo=timezone.utc) assert ( is_due_on_date( schedule=schedule, next_due_at=missed_monday, on_date=date(2026, 8, 18) ) is True ) def test_weekday_mask_for_monday(): assert weekday_mask_for_date(date(2026, 8, 17)) == weekday_bit(0) def test_pause_until_pushes_initial_due(): schedule = { "schedule_kind": "interval", "interval_days": 1, "weekday_mask": 0, "pause_until": "2026-08-10", } now = datetime(2026, 8, 4, 9, 0, tzinfo=timezone.utc) due = initial_due_at(schedule=schedule, now=now) assert due.date() == date(2026, 8, 11)